<?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.date.php");
require_once(APP_INC_PATH . "class.mime_helper.php");
require_once(APP_INC_PATH . "class.setup.php");
require_once(APP_INC_PATH . "class.lock.php");
require_once(APP_INC_PATH . "class.user.php");
require_once(APP_PEAR_PATH . 'Mail.php');
class Mail_Queue
{
/**
* Checks whether it is safe or not to run the mail queue script.
*
* @access public
* @return boolean
*/
function isSafeToRun()
{
return Lock::acquire('process_mail_queue');
}
/**
* Clears the lock file for the next time this script runs again.
*
* @access public
* @return void
*/
function removeProcessFile()
{
Lock::release('process_mail_queue');
}
/**
* Adds an email to the outgoing mail queue.
*
* @access public
* @param string $recipient The recipient of this email
* @param array $headers The list of headers that should be sent with this email
* @param string $body The body of the message
* @param integer $save_email_copy Whether to send a copy of this email to a configurable address or not (eventum_sent@)
* @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 true, or a PEAR_Error object
*/
function add($recipient, $headers, $body, $save_email_copy = 0, $issue_id = false, $type = '', $sender_usr_id = false, $type_id = false)
{
Workflow::modifyMailQueue(Auth::getCurrentProject(), $recipient, $headers, $body, $issue_id, $type, $sender_usr_id, $type_id);
// avoid sending emails out to users with inactive status
$recipient_email = Mail_API::getEmailAddress($recipient);
$usr_id = User::getUserIDByEmail($recipient_email);
if (!empty($usr_id)) {
$user_status = User::getStatusByEmail($recipient_email);
// if user is not set to an active status, then silently ignore
if ((!User::isActiveStatus($user_status)) && (!User::isPendingStatus($user_status))) {
return false;
}
}
$to_usr_id = User::getUserIDByEmail($recipient_email);
$recipient = Mail_API::fixAddressQuoting($recipient);
$reminder_addresses = Reminder::_getReminderAlertAddresses();
// add specialized headers
if ((!empty($issue_id)) && ((!empty($to_usr_id)) && (User::getRoleByUser($to_usr_id, Issue::getProjectID($issue_id)) != User::getRoleID("Customer"))) ||
(@in_array(Mail_API::getEmailAddress($to), $reminder_addresses))) {
$headers += Mail_API::getSpecializedHeaders($issue_id, $type, $headers, $sender_usr_id);
}
$headers['precedence'] = 'bulk';
if (empty($issue_id)) {
$issue_id = 'null';
}
// if the Date: header is missing, add it.
if (!in_array('Date', array_keys($headers))) {
$headers['Date'] = MIME_Helper::encode(date('D, j M Y H:i:s O'));
}
if (!empty($headers['To'])) {
$headers['To'] = Mail_API::fixAddressQuoting($headers['To']);
}
// encode headers and add special mime headers
$headers = Mime_Helper::encodeHeaders($headers);
$res = Mail_API::prepareHeaders($headers);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return $res;
}
// convert array of headers into text headers
list(,$text_headers) = $res;
$stmt = "INSERT INTO
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
(
maq_save_copy,
maq_queued_date,
maq_sender_ip_address,
maq_recipient,
maq_headers,
maq_body,
maq_iss_id,
maq_subject,
maq_type";
if ($sender_usr_id != false) {
$stmt .= ",\nmaq_usr_id";
}
if ($type_id != false) {
$stmt .= ",\nmaq_type_id";
}
$stmt .= ") VALUES (
$save_email_copy,
'" . Date_API::getCurrentDateGMT() . "',
'" . @$_SERVER['REMOTE_ADDR'] . "',
'" . Misc::escapeString($recipient) . "',
'" . Misc::escapeString($text_headers) . "',
'" . Misc::escapeString($body) . "',
" . Misc::escapeInteger($issue_id) . ",
'" . Misc::escapeString($headers["Subject"]) . "',
'$type'";
if ($sender_usr_id != false) {
$stmt .= ",\n" . $sender_usr_id;
}
if ($type_id != false) {
$stmt .= ",\n" . $type_id;
}
$stmt .= ")";
$res = $GLOBALS["db_api"]->dbh->query($stmt);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return $res;
} else {
return true;
}
}
/**
* Sends the queued up messages to their destinations. This can either try
* to send emails that couldn't be sent before (status = 'error'), or just
* emails just recently queued (status = 'pending').
*
* @access public
* @param string $status The status of the messages that need to be sent
* @param integer $limit The limit of emails that we should send at one time
*/
function send($status, $limit)
{
// get list of emails to send
$emails = Mail_Queue::_getList($status, $limit);
// foreach email
for ($i = 0; $i < count($emails); $i++) {
$current_status = $status;
if ($emails[$i]['maq_type'] == 'error') {
$current_status = 'error';
}
$result = Mail_Queue::_sendEmail($emails[$i]['recipient'], $emails[$i]['headers'], $emails[$i]['body'], $current_status);
if (PEAR::isError($result)) {
Mail_Queue::_saveLog($emails[$i]['id'], 'error', Mail_Queue::_getErrorMessage($result));
} else {
Mail_Queue::_saveLog($emails[$i]['id'], 'sent', '');
if ($emails[$i]['save_copy']) {
// send a copy of this email to eventum_sent@
Mail_API::saveEmailInformation($emails[$i]);
}
}
}
}
/**
* Connects to the SMTP server and sends the queued message.
*
* @access private
* @param string $recipient The recipient of this message
* @param string $text_headers The full headers of this message
* @param string $body The full body of this message
* @param string $status The status of this message
* @return true, or a PEAR_Error object
*/
function _sendEmail($recipient, $text_headers, $body, $status)
{
$header_names = Mime_Helper::getHeaderNames($text_headers);
$_headers = Mail_Queue::_getHeaders($text_headers, $body);
$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']);
// mutt sucks, so let's remove the broken Mime-Version header and add the proper one
if (in_array('Mime-Version', array_keys($headers))) {
unset($headers['Mime-Version']);
$headers['MIME-Version'] = '1.0';
}
$mail =& Mail::factory('smtp', Mail_API::getSMTPSettings());
$res = $mail->send($recipient, $headers, $body);
if (PEAR::isError($res)) {
// special handling of errors when the mail server is down
$msg = $res->getMessage();
$cant_notify = (($status == 'error') || (strstr($msg , 'unable to connect to smtp server')) || (stristr($msg, 'Failed to connect to') !== false));
Error_Handler::logError(array($msg, $res->getDebugInfo()), __FILE__, __LINE__, !$cant_notify);
return $res;
} else {
return true;
}
}
/**
* Parses the full email message and returns an array of the headers
* contained in it.
*
* @access private
* @param string $text_headers The full headers of this message
* @param string $body The full body of this message
* @return array The list of headers
*/
function _getHeaders($text_headers, $body)
{
$message = $text_headers . "\n\n" . $body;
$structure = Mime_Helper::decode($message, FALSE, FALSE);
return $structure->headers;
}
/**
* Retrieves the list of queued email messages, given a status.
*
* @access private
* @param string $status The status of the messages
* @param integer $limit The limit on the number of messages that need to be returned
* @return array The list of queued email messages
*/
function _getList($status, $limit = 50)
{
$stmt = "SELECT
maq_id id,
maq_iss_id,
maq_save_copy save_copy,
maq_recipient recipient,
maq_headers headers,
maq_body body,
maq_type,
maq_usr_id
FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
WHERE
maq_status='$status'
ORDER BY
maq_id ASC
LIMIT
0, $limit";
$res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return array();
} else {
return $res;
}
}
/**
* Saves a log entry about the attempt, successful or otherwise, to send the
* queued email message.
*
* @access private
* @param integer $maq_id The queued email message ID
* @param string $status The status of the attempt ('sent' or 'error')
* @param string $server_message The full message from the SMTP server, in case of an error
* @return boolean
*/
function _saveLog($maq_id, $status, $server_message)
{
$stmt = "INSERT INTO
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue_log
(
mql_maq_id,
mql_created_date,
mql_status,
mql_server_message
) VALUES (
$maq_id,
'" . Date_API::getCurrentDateGMT() . "',
'" . Misc::escapeString($status) . "',
'" . Misc::escapeString($server_message) . "'
)";
$res = $GLOBALS["db_api"]->dbh->query($stmt);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return false;
} else {
$stmt = "UPDATE
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
SET
maq_status='" . Misc::escapeString($status) . "'
WHERE
maq_id=$maq_id";
$GLOBALS["db_api"]->dbh->query($stmt);
return true;
}
}
/**
* Handles the PEAR_Error object returned from the SMTP server, and returns
* an appropriate error message string.
*
* @access private
* @param object $error The PEAR_Error object
* @return string The error message
*/
function _getErrorMessage($error)
{
return $error->getMessage() . "/" . $error->getDebugInfo();
}
/**
* Returns the mail queue for a specific issue.
*
* @access public
* @param integer $issue_is The issue ID
* @return array An array of emails from the queue
*/
function getListByIssueID($issue_id)
{
$issue_id = Misc::escapeInteger($issue_id);
$stmt = "SELECT
maq_id,
maq_queued_date,
maq_status,
maq_recipient,
maq_subject
FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
WHERE
maq_iss_id = " . Misc::escapeInteger($issue_id) . "
ORDER BY
maq_queued_date ASC";
$res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return false;
}
if (count($res) > 0) {
for ($i = 0; $i < count($res); $i++) {
$res[$i]['maq_recipient'] = Mime_Helper::decodeAddress($res[$i]['maq_recipient']);
$res[$i]['maq_queued_date'] = Date_API::getFormattedDate(Date_API::getUnixTimestamp($res[$i]['maq_queued_date'], 'GMT'));
$res[$i]['maq_subject'] = Mime_Helper::fixEncoding($res[$i]['maq_subject']);
}
}
return $res;
}
/**
* Returns the mail queue entry based on ID.
*
* @acess public
* @param integer $maq_id The id of the mail queue entry.
* @return array An array of information
*/
function getEntry($maq_id)
{
$stmt = "SELECT
maq_iss_id,
maq_queued_date,
maq_status,
maq_recipient,
maq_subject,
maq_headers,
maq_body
FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
WHERE
maq_id = " . Misc::escapeInteger($maq_id);
$res = $GLOBALS["db_api"]->dbh->getRow($stmt, DB_FETCHMODE_ASSOC);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return false;
} else {
return $res;
}
}
function getMessageRecipients($types, $type_id)
{
if (!is_array($types)) {
$types = array($types);
}
$types = Misc::escapeString($types);
$sql = "SELECT
maq_recipient
FROM
" . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "mail_queue
WHERE
maq_type IN('" . join("', '", $types) . "') AND
maq_type_id = " . Misc::escapeInteger($type_id);
$res = $GLOBALS["db_api"]->dbh->getCol($sql);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
return false;
} else {
for ($i = 0; $i < count($res); $i++) {
$res[$i] = Mime_Helper::decodeAddress(str_replace('"', '', $res[$i]));
}
return $res;
}
}
}
// benchmarking the included file (aka setup time)
if (APP_BENCHMARK) {
$GLOBALS['bench']->setMarker('Included Mail_Queue Class');
}