diff --git a/include/class.mailer.php b/include/class.mailer.php index b3c8c2111af544e231a564c3556d91bcfd4ffed1..3faae326690d2fe129549da8d4594b4041c0a5e3 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -290,25 +290,27 @@ class Mailer { 0, 6); } - function send($to, $subject, $message, $options=null) { + function send($recipient, $subject, $message, $options=null) { global $ost, $cfg; //Get the goodies require_once (PEAR_DIR.'Mail.php'); // PEAR Mail package require_once (PEAR_DIR.'Mail/mime.php'); // PEAR Mail_Mime packge - $messageId = $this->getMessageId($to, $options); + $messageId = $this->getMessageId($recipient, $options); - if (is_object($to) && is_callable(array($to, 'getEmail'))) { + if (is_object($recipient) && is_callable(array($recipient, 'getEmail'))) { // Add personal name if available - if (is_callable(array($to, 'getName'))) { + if (is_callable(array($recipient, 'getName'))) { $to = sprintf('"%s" <%s>', - $to->getName()->getOriginal(), $to->getEmail() + $recipient->getName()->getOriginal(), $recipient->getEmail() ); } else { - $to = $to->getEmail(); + $to = $recipient->getEmail(); } + } else { + $to = $recipient; } //do some cleanup @@ -327,16 +329,58 @@ class Mailer { // Add in the options passed to the constructor $options = ($options ?: array()) + $this->options; + // Message Id Token + $mid_token = ''; + // Check if the email is threadable + if (isset($options['thread']) + && $options['thread'] instanceof ThreadEntry + && ($thread = $options['thread']->getThread())) { + + // Add email in-reply-to references if not set + if (!isset($options['inreplyto'])) { + + $entry = null; + switch (true) { + case $recipient instanceof TicketOwner: + case $recipient instanceof Collaborator: + $entry = $thread->getLastEmailMessage(array( + 'user_id' => $recipient->getUserId())); + break; + case $recipient instanceof Staff: + //XXX: is it necessary ?? + break; + } + + if ($entry && ($mid=$entry->getEmailMessageId())) { + $options['inreplyto'] = $mid; + $options['references'] = $entry->getEmailReferences(); + } + } + + // Embedded message id token + $mid_token = $messageId; + // Set Reply-Tag + if (!isset($options['reply-tag'])) { + if ($cfg && $cfg->stripQuotedReply()) + $options['reply-tag'] = $cfg->getReplySeparator() . '<br/><br/>'; + else + $options['reply-tag'] = ''; + } elseif ($options['reply-tag'] === false) { + $options['reply-tag'] = ''; + } + } + + // Return-Path if (isset($options['nobounce']) && $options['nobounce']) $headers['Return-Path'] = '<>'; elseif ($this->getEmail() instanceof Email) $headers['Return-Path'] = $this->getEmail()->getEmail(); - //Bulk. + // Bulk. if (isset($options['bulk']) && $options['bulk']) $headers+= array('Precedence' => 'bulk'); - //Auto-reply - mark as autoreply and supress all auto-replies + // Auto-reply - mark as autoreply and supress all auto-replies if (isset($options['autoreply']) && $options['autoreply']) { $headers+= array( 'Precedence' => 'auto_reply', @@ -345,51 +389,22 @@ class Mailer { 'Auto-Submitted' => 'auto-replied'); } - //Notice (sort of automated - but we don't want auto-replies back + // Notice (sort of automated - but we don't want auto-replies back if (isset($options['notice']) && $options['notice']) $headers+= array( 'X-Auto-Response-Suppress' => 'OOF, AutoReply', 'Auto-Submitted' => 'auto-generated'); - - if ($options) { - if (isset($options['inreplyto']) && $options['inreplyto']) - $headers += array('In-Reply-To' => $options['inreplyto']); - if (isset($options['references']) && $options['references']) { - if (is_array($options['references'])) - $headers += array('References' => - implode(' ', $options['references'])); - else - $headers += array('References' => $options['references']); - } - } - - // Make the best effort to add In-Reply-To and References headers - $reply_tag = $mid_token = ''; - if (isset($options['thread']) - && $options['thread'] instanceof ThreadEntry - ) { - if ($irt = $options['thread']->getEmailMessageId()) { - // This is an response from an email, like and autoresponse. - // Web posts will not have a email message-id - $headers += array( - 'In-Reply-To' => $irt, - 'References' => $options['thread']->getEmailReferences() - ); - } - elseif ($original = $options['thread']->findOriginalEmailMessage()) { - // Use the parent item as the email information source. This - // will apply for staff replies - $headers += array( - 'In-Reply-To' => $original->getEmailMessageId(), - 'References' => $original->getEmailReferences(), - ); - } - - // Configure the reply tag and embedded message id token - $mid_token = $messageId; - if ($cfg && $cfg->stripQuotedReply() - && (!isset($options['reply-tag']) || $options['reply-tag'])) - $reply_tag = $cfg->getReplySeparator() . '<br/><br/>'; + // In-Reply-To + if (isset($options['inreplyto']) && $options['inreplyto']) + $headers += array('In-Reply-To' => $options['inreplyto']); + + // References + if (isset($options['references']) && $options['references']) { + if (is_array($options['references'])) + $headers += array('References' => + implode(' ', $options['references'])); + else + $headers += array('References' => $options['references']); } // Use general failsafe default initially @@ -419,10 +434,14 @@ class Mailer { if (!(isset($options['text']) && $options['text'])) { // Embed the data-mid in such a way that it should be included // in a response - if ($reply_tag || $mid_token) { - $message = "<div style=\"display:none\" - class=\"mid-$mid_token\">$reply_tag</div>$message"; + if ($options['reply-tag'] || $mid_token) { + $message = sprintf('<div style="display:none" + class="mid-%s">%s</div>%s', + $mid_token, + $options['reply-tag'], + $message); } + $txtbody = rtrim(Format::html2text($message, 90, false)) . ($messageId ? "\nRef-Mid: $messageId\n" : ''); $mime->setTXTBody($txtbody); diff --git a/include/class.task.php b/include/class.task.php index d86a52ef766d2a89ede2b410a50878116501b0bb..c3b23bb694c92aea9da74c3e48ac7d81069929ae 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -675,10 +675,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { // Send the alerts. $sentlist = array(); $options = $note instanceof ThreadEntry - ? array( - 'inreplyto' => $note->getEmailMessageId(), - 'references' => $note->getEmailReferences(), - 'thread' => $note) + ? array('thread' => $note) : array(); foreach ($recipients as $k => $staff) { @@ -765,10 +762,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { $sentlist = $options = array(); if ($note instanceof ThreadEntry) { - $options += array( - 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences(), - 'thread'=>$note); + $options += array('thread'=>$note); } foreach ($recipients as $k=>$staff) { @@ -1018,10 +1012,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { $options = array(); $staffId = $thisstaff ? $thisstaff->getId() : 0; if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) { - $options = array( - 'inreplyto' => $vars['threadentry']->getEmailMessageId(), - 'references' => $vars['threadentry']->getEmailReferences(), - 'thread' => $vars['threadentry']); + $options = array('thread' => $vars['threadentry']); // Activity details if (!$vars['message']) @@ -1104,8 +1095,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { $msg = $this->replaceVars($msg->asArray(), $vars); $attachments = $cfg->emailAttachments()?$entry->getAttachments():array(); - $options = array('inreplyto' => $entry->getEmailMessageId(), - 'thread' => $entry); + $options = array('thread' => $entry); foreach ($recipients as $recipient) { // Skip folks who have already been included on this part of diff --git a/include/class.thread.php b/include/class.thread.php index 9f32b0f90521fd8682bbdc27350c589c75b2143f..b1e8445211bb52ce6f19d8700dffd12ab0758432 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -53,6 +53,7 @@ class Thread extends VerySimpleModel { const MODE_CLIENT = 2; var $_object; + var $_entries; var $_collaborators; // Cache for collabs var $_participants; @@ -87,7 +88,6 @@ class Thread extends VerySimpleModel { return $this->entries->count(); } - var $_entries; function getEntries($criteria=false) { if (!isset($this->_entries)) { $this->_entries = $this->entries->annotate(array( @@ -2399,44 +2399,68 @@ implements TemplateVariable { return $this->counts[NoteThreadEntry::ENTRY_TYPE]; } - function getMessages() { - return $this->entries->filter(array( - 'type' => MessageThreadEntry::ENTRY_TYPE - )); - } function getLastMessage($criteria=false) { - $entries = $this->entries->filter(array( + $entries = clone $this->getEntries(); + $entries->filter(array( 'type' => MessageThreadEntry::ENTRY_TYPE )); + if ($criteria) $entries->filter($criteria); - return $entries->order_by('-id')->first(); + $entries->order_by('-id'); + + return $entries->first(); + } + + function getLastEmailMessage($criteria=array()) { + + $criteria += array( + 'source' => 'Email', + 'email_info__headers__isnull' => false); + + return $this->getLastMessage($criteria); + } + + function getLastEmailMessageByUser($user) { + + $uid = is_numeric($user) ? $user : 0; + if (!$uid && ($user instanceof EmailContact)) + $uid = $user->getUserId(); + + return $uid + ? $this->getLastEmailMessage(array('user_id' => $uid)) + : null; } - function getEntry($var) { + function getEntry($criteria) { // XXX: PUNT - if (is_numeric($var)) - $id = $var; - else { - $criteria = array_merge($var, array('limit' => 1)); - $entries = $this->getEntries($criteria); - if ($entries && $entries[0]) - $id = $entries[0]['id']; - } + if (is_numeric($criteria)) + return parent::getEntry($criteria); - return $id ? parent::getEntry($id) : null; + $entries = clone $this->getEntries(); + $entries->filter($criteria); + return $entries->first(); + } + + function getMessages() { + $entries = clone $this->getEntries(); + return $entries->filter(array( + 'type' => MessageThreadEntry::ENTRY_TYPE + )); } function getResponses() { - return $this->entries->filter(array( + $entries = clone $this->getEntries(); + return $entries->filter(array( 'type' => ResponseThreadEntry::ENTRY_TYPE )); } function getNotes() { - return $this->entries->filter(array( + $entries = clone $this->getEntries(); + return $entries->filter(array( 'type' => NoteThreadEntry::ENTRY_TYPE )); } diff --git a/include/class.ticket.php b/include/class.ticket.php index 86bc673892750bde1380ea282878a447d0083474..c97ebbd2e4f9e329f20530047283d992255593d2 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1306,7 +1306,8 @@ implements RestrictedAccess, Threadable { } $options = array(); - if ($message instanceof ThreadEntry) { + if (($message instanceof ThreadEntry) + && $message->getEmailMessageId()) { $options += array( 'inreplyto'=>$message->getEmailMessageId(), 'references'=>$message->getEmailReferences(), @@ -1494,8 +1495,7 @@ implements RestrictedAccess, Threadable { $msg = $this->replaceVars($msg->asArray(), $vars); $attachments = $cfg->emailAttachments()?$entry->getAttachments():array(); - $options = array('inreplyto' => $entry->getEmailMessageId(), - 'thread' => $entry); + $options = array('thread' => $entry); foreach ($recipients as $recipient) { // Skip folks who have already been included on this part of // the conversation @@ -1583,10 +1583,14 @@ implements RestrictedAccess, Threadable { 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'' ) ); - $options = array( - 'inreplyto' => $message->getEmailMessageId(), - 'thread' => $message - ); + $options = array('thread' => $message); + if ($message->getEmailMessageId()) { + $options += array( + 'inreplyto' => $message->getEmailMessageId(), + 'references' => $message->getEmailReferences() + ); + } + $email->sendAutoReply($user, $msg['subj'], $msg['body'], null, $options); } @@ -1633,10 +1637,7 @@ implements RestrictedAccess, Threadable { $options = array(); $staffId = $thisstaff ? $thisstaff->getId() : 0; if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) { - $options = array( - 'inreplyto' => $vars['threadentry']->getEmailMessageId(), - 'references' => $vars['threadentry']->getEmailReferences(), - 'thread' => $vars['threadentry']); + $options = array('thread' => $vars['threadentry']); // Activity details if (!$vars['comments']) @@ -1731,10 +1732,7 @@ implements RestrictedAccess, Threadable { // Send the alerts. $sentlist = array(); $options = $note instanceof ThreadEntry - ? array( - 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences(), - 'thread'=>$note) + ? array('thread'=>$note) : array(); foreach ($recipients as $k=>$staff) { if (!is_object($staff) @@ -2064,10 +2062,7 @@ implements RestrictedAccess, Threadable { } $sentlist = $options = array(); if ($note) { - $options += array( - 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences(), - 'thread'=>$note); + $options += array('thread'=>$note); } foreach ($recipients as $k=>$staff) { if (!is_object($staff) @@ -2308,7 +2303,7 @@ implements RestrictedAccess, Threadable { if ($alerts && $message->isBounceOrAutoReply()) $alerts = false; - if ($alerts && $this->getThread()->getLastMessage(array( + if ($alerts && $this->getThread()->getLastEmailMessage(array( 'user_id' => $message->user_id, 'id__lt' => $message->id, 'created__gt' => SqlFunction::NOW()->minus(SqlInterval::MINUTE(5)), @@ -2330,11 +2325,8 @@ implements RestrictedAccess, Threadable { 'message' => $message, 'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName()) ); - $options = array( - 'inreplyto' => $message->getEmailMessageId(), - 'references' => $message->getEmailReferences(), - 'thread'=>$message - ); + + $options = array('thread'=>$message); // If enabled...send alert to staff (New Message Alert) if ($cfg->alertONNewMessage() && ($email = $dept->getAlertEmail()) @@ -2443,11 +2435,18 @@ implements RestrictedAccess, Threadable { ); $attachments = ($cfg->emailAttachments() && $files) ? $response->getAttachments() : array(); - $options = array( - 'inreplyto'=>$response->getEmailMessageId(), - 'references'=>$response->getEmailReferences(), - 'thread'=>$response); - $email->sendAutoReply($this, $msg['subj'], $msg['body'], $attachments, + + $options = array('thread' => $response); + if (($message instanceof ThreadEntry) + && $message->getUserId() == $this->getUserId() + && ($mid=$message->getEmailMessageId())) { + $options += array( + 'inreplyto' => $mid, + 'references' => $message->getEmailReferences() + ); + } + + $email->sendAutoReply($this->getOwner(), $msg['subj'], $msg['body'], $attachments, $options); } return $response; @@ -2511,21 +2510,19 @@ implements RestrictedAccess, Threadable { 'staff' => $thisstaff, 'poster' => $thisstaff ); - $options = array( - 'inreplyto' => $response->getEmailMessageId(), - 'references' => $response->getEmailReferences(), - 'thread'=>$response - ); + + $user = $this->getOwner(); + $options = array('thread' => $response); if (($email=$dept->getEmail()) && ($tpl = $dept->getTemplate()) && ($msg=$tpl->getReplyMsgTemplate()) ) { $msg = $this->replaceVars($msg->asArray(), - $variables + array('recipient' => $this->getOwner()) + $variables + array('recipient' => $user) ); $attachments = $cfg->emailAttachments()?$response->getAttachments():array(); - $email->send($this->getOwner(), $msg['subj'], $msg['body'], $attachments, + $email->send($user, $msg['subj'], $msg['body'], $attachments, $options); } @@ -3558,14 +3555,8 @@ implements RestrictedAccess, Threadable { 'staff' => $thisstaff, ) ); - $references = array(); $message = $ticket->getLastMessage(); - if (isset($message)) - $references[] = $message->getEmailMessageId(); - if (isset($response)) - $references[] = $response->getEmailMessageId(); $options = array( - 'references' => $references, 'thread' => $message ?: $ticket->getThread(), ); $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments,