diff --git a/include/class.format.php b/include/class.format.php index 2a9934a6dd1342ca5e9dc80212bec2e0788c5397..c6a45798bb006c7a3be7741a2b4ff4f2410dd6b2 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -217,7 +217,7 @@ class Format { 'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data', 'hook_tag' => function($e, $a=0) { return Format::__html_cleanup($e, $a); }, 'elements' => '*+iframe', - 'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder;', + 'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder; div=data-mid', ); return Format::html($html, $config); diff --git a/include/class.mailer.php b/include/class.mailer.php index 1c43398bcf4d3af04540fdbe6c01fbc181d2f2e2..41d82e8c83afb7818633ed102e0d067c26e5aad6 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -101,7 +101,7 @@ class Mailer { /* Message ID - generated for each outgoing email */ $messageId = sprintf('<%s-%s>', Misc::randCode(16), - ($this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer')); + ($this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer')); $headers = array ( 'From' => $this->getFromAddress(), @@ -151,10 +151,17 @@ class Mailer { // then assume that it needs html processing to create a valid text // body $isHtml = true; + $mid_token = (isset($options['thread'])) + ? $options['thread']->asMessageId($to) : ''; if (!(isset($options['text']) && $options['text'])) { + if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) + $message = "<div style=\"display:none\" data-mid=\"$mid_token\">$tag</div>" + .$message; // Make sure nothing unsafe has creeped into the message $message = Format::safe_html($message); //XXX?? - $mime->setTXTBody(Format::html2text($message, 90, false)); + $txtbody = rtrim(Format::html2text($message, 90, false)) + . ($mid_token ? "\nRef-Mid: $mid_token\n" : ''); + $mime->setTXTBody($txtbody); } else { $mime->setTXTBody($message); diff --git a/include/class.thread.php b/include/class.thread.php index fec7c52c40d9284a18ef3f4a6fca9191db3a0713..35ec91788601d9446f926fdf08136643a81f9670 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -178,13 +178,11 @@ class Thread { function delete() { - /* XXX: Leave this out until TICKET_EMAIL_INFO_TABLE has a primary - * key - $sql = 'DELETE mid.* FROM '.TICKET_EMAIL_INFO_TABLE.' mid + $sql = 'UPDATE '.TICKET_EMAIL_INFO_TABLE.' mid INNER JOIN '.TICKET_THREAD_TABLE.' thread ON (thread.id = mid.thread_id) - WHERE thread.ticket_id = '.db_input($this->getTicketId()); + SET mid.headers = null WHERE thread.ticket_id = ' + .db_input($this->getTicketId()); db_query($sql); - */ $res=db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getTicketId())); if(!$res || !db_affected_rows()) @@ -854,9 +852,67 @@ Class ThreadEntry { } } + // Search for the message-id token in the body + if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`', + $mailinfo['message'], $match)) + if ($thread = ThreadEntry::lookupByMessageId($match[1])) + return $thread; + return null; } + /** + * Find a thread entry from a message-id created from the + * ::asMessageId() method + */ + function lookupByMessageId($mid, $from) { + $mid = trim($mid, '<>'); + list($ver, $ids, $mails) = explode('$', $mid, 3); + + // Current version is <null> + if ($ver !== '') + return false; + + $ids = @unpack('Vthread', base64_decode($ids)); + if (!$ids || !$ids['thread']) + return false; + + $thread = ThreadEntry::lookup($ids['thread']); + if (!$thread) + return false; + + if (0 === strcasecmp($thread->asMessageId($from, $vers), $mid)) + return $thread; + } + + /** + * Get an email message-id that can be used to represent this thread + * entry. The same message-id can be passed to ::lookupByMessageId() to + * find this thread entry + * + * Formats: + * Initial (version <null>) + * <$:b32(thread-id)$:md5(to-addr.ticket-num.ticket-id)@:md5(url)> + * thread-id - thread-id, little-endian INT, packed + * :b32() - base32 encoded + * to-addr - individual email recipient + * ticket-num - external ticket number + * ticket-id - internal ticket id + * :md5() - last 10 hex chars of MD5 sum + * url - helpdesk URL + */ + function asMessageId($to, $version=false) { + global $ost; + + $domain = md5($ost->getConfig()->getURL()); + $ticket = $this->getTicket(); + return sprintf('$%s$%s@%s', + base64_encode(pack('V', $this->getId())), + substr(md5($to . $ticket->getNumber() . $ticket->getId()), -10), + substr($domain, -10) + ); + } + //new entry ... we're trusting the caller to check validity of the data. function create($vars) { global $cfg; diff --git a/include/class.ticket.php b/include/class.ticket.php index df83644a8da74e58bda87435ab10701eb9adf1a2..124c5ff3b441ef5a078bcb4dc9f20f78d13d0b46 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -874,7 +874,8 @@ class Ticket { $options = array( 'inreplyto'=>$message->getEmailMessageId(), - 'references'=>$message->getEmailReferences()); + 'references'=>$message->getEmailReferences(), + 'thread'=>$message); //Send auto response - if enabled. if($autorespond @@ -888,9 +889,6 @@ class Ticket { 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'') ); - if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], null, $options); } @@ -1067,13 +1065,10 @@ class Ticket { 'recipient' => $user, 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); - //Reply separator tag. - if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $options = array( - 'inreplyto' => $message->getEmailMessageId(), - 'references' => $message->getEmailReferencesForUser($user)); + 'inreplyto'=>$message->getEmailMessageId(), + 'references' => $message->getEmailReferencesForUser($user), + 'thread'=>$message); $email->sendAutoReply($user->getEmail(), $msg['subj'], $msg['body'], null, $options); } @@ -1130,7 +1125,8 @@ class Ticket { $sentlist=array(); $options = array( 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences()); + 'references'=>$note->getEmailReferences(), + 'thread'=>$note); foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = $this->replaceVars($msg, array('recipient' => $staff)); @@ -1381,7 +1377,8 @@ class Ticket { $sentlist=array(); $options = array( 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences()); + 'references'=>$note->getEmailReferences(), + 'thread'=>$note); foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = $this->replaceVars($msg, array('recipient' => $staff)); @@ -1568,7 +1565,8 @@ class Ticket { ); $options = array( 'inreplyto' => $message->getEmailMessageId(), - 'references' => $message->getEmailReferences()); + 'references' => $message->getEmailReferences(), + 'thread'=>$message); //If enabled...send alert to staff (New Message Alert) if($cfg->alertONNewMessage() && ($email = $cfg->getAlertEmail()) @@ -1643,13 +1641,11 @@ class Ticket { $msg = $this->replaceVars($msg->asArray(), array('response' => $response, 'signature' => $signature)); - if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array(); $options = array( 'inreplyto'=>$response->getEmailMessageId(), - 'references'=>$response->getEmailReferences()); + 'references'=>$response->getEmailReferences(), + 'thread'=>$response); $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments, $options); } @@ -1700,7 +1696,8 @@ class Ticket { 'poster' => $thisstaff); $options = array( 'inreplyto' => $response->getEmailMessageId(), - 'references' => $response->getEmailReferences()); + 'references' => $response->getEmailReferences(), + 'thread'=>$response); if(($email=$dept->getEmail()) && ($tpl = $dept->getTemplate()) @@ -1709,8 +1706,6 @@ class Ticket { $msg = $this->replaceVars($msg->asArray(), $variables + array('recipient' => $this->getOwner())); - if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; $attachments = $cfg->emailAttachments()?$response->getAttachments():array(); $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments, $options); @@ -1824,7 +1819,8 @@ class Ticket { $options = array( 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences()); + 'references'=>$note->getEmailReferences(), + 'thread'=>$note); $sentlist=array(); foreach( $recipients as $k=>$staff) { if(!is_object($staff) @@ -2457,13 +2453,13 @@ class Ticket { ) ); - if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator()))) - $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $references = $ticket->getLastMessage()->getEmailMessageId(); if (isset($response)) $references = array($response->getEmailMessageId(), $references); - $options = array('references' => $references); + $options = array( + 'references' => $references, + 'thread'=>$this->getLastMessage() + ); $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments, $options); }