[ Index ] |
PHP Cross Reference of Eventum |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 encoding=utf-8: */ 3 // +----------------------------------------------------------------------+ 4 // | Eventum - Issue Tracking System | 5 // +----------------------------------------------------------------------+ 6 // | Copyright (c) 2003, 2004, 2005, 2006, 2007 MySQL AB | 7 // | | 8 // | This program is free software; you can redistribute it and/or modify | 9 // | it under the terms of the GNU General Public License as published by | 10 // | the Free Software Foundation; either version 2 of the License, or | 11 // | (at your option) any later version. | 12 // | | 13 // | This program is distributed in the hope that it will be useful, | 14 // | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 // | GNU General Public License for more details. | 17 // | | 18 // | You should have received a copy of the GNU General Public License | 19 // | along with this program; if not, write to: | 20 // | | 21 // | Free Software Foundation, Inc. | 22 // | 59 Temple Place - Suite 330 | 23 // | Boston, MA 02111-1307, USA. | 24 // +----------------------------------------------------------------------+ 25 // | Authors: João Prado Maia <jpm@mysql.com> | 26 // +----------------------------------------------------------------------+ 27 // 28 29 require_once (APP_INC_PATH . "class.error_handler.php"); 30 require_once (APP_INC_PATH . "class.setup.php"); 31 require_once (APP_INC_PATH . "class.mail_queue.php"); 32 require_once (APP_INC_PATH . "class.user.php"); 33 require_once (APP_INC_PATH . "class.mime_helper.php"); 34 require_once (APP_INC_PATH . "class.reminder.php"); 35 require_once(APP_PEAR_PATH . "Mail/RFC822.php"); 36 37 /** 38 * Class to handle the business logic related to sending email to 39 * outside recipients. This class utilizes the PEAR::Mail 40 * infrastructure to deliver email in a compatible way across 41 * different platforms. 42 * 43 * @version 1.0 44 * @author João Prado Maia <jpm@mysql.com> 45 */ 46 47 class Mail_API 48 { 49 // variable to keep the Mail_mime object 50 var $mime; 51 // variable to keep the headers to be used in the email 52 var $headers = ''; 53 // text version of this message 54 var $text_body = ''; 55 56 57 /** 58 * Class constructor. It includes and initializes the required 59 * PEAR::Mail related objects 60 * 61 * @access public 62 */ 63 function Mail_API() 64 { 65 @require_once(APP_PEAR_PATH . 'Mail.php'); 66 @require_once(APP_PEAR_PATH . 'Mail/mime.php'); 67 $this->mime = new Mail_mime("\r\n"); 68 } 69 70 71 /** 72 * Correctly formats the subject line of outgoing emails/notes 73 * 74 * @access public 75 * @param integer $issue_id The issue ID 76 * @param string $subject The subject to be formatted 77 * @return string The formatted subject 78 */ 79 function formatSubject($issue_id, $subject) 80 { 81 return "[#$issue_id] " . trim(preg_replace("/\[#$issue_id\] {0,1}/", '', $subject)); 82 } 83 84 85 /** 86 * Believe it or not, this is a method that will remove excess occurrences 87 * of 'Re:' that commonly are found in email subject lines. 88 * If the second parameter is true, issue #'s will also be stripped. 89 * 90 * @access public 91 * @param string $subject The subject line 92 * @param boolean $remove_issue_id If the issue ID should be removed 93 * @return string The subject line with the extra occurrences removed from it 94 */ 95 function removeExcessRe($subject, $remove_issue_id = false) 96 { 97 if ($remove_issue_id) { 98 $subject = trim(preg_replace("/\[#\d+\] {0,1}/", '', $subject)); 99 } 100 $re_pattern = "/(\[#\d+\] ){0,1}(([Rr][Ee][Ss]?|Îòâåò|Antwort|SV|[Aa][Ww])(\[[0-9]+\])?[ \t]*: ){2}(.*)/"; 101 if (preg_match($re_pattern, $subject, $matches)) { 102 $subject = preg_replace($re_pattern, '$1Re: $5', $subject); 103 return Mail_API::removeExcessRe($subject); 104 } else { 105 return $subject; 106 } 107 } 108 109 110 /** 111 * Returns the canned explanation about why an email message was blocked 112 * and saved into an internal note. 113 * 114 * @access public 115 * @return string The canned explanation 116 */ 117 function getCannedBlockedMsgExplanation() 118 { 119 $msg = ev_gettext("WARNING: This message was blocked because the sender was not allowed to send emails to the associated issue.") . " "; 120 $msg .= ev_gettext("Only staff members listed in the assignment or authorized replier fields can send emails.") . "\n"; 121 $msg .= str_repeat('-', 70) . "\n\n"; 122 return $msg; 123 } 124 125 126 /** 127 * Checks whether the given headers are from a vacation 128 * auto-responder message or not. 129 * 130 * @access public 131 * @param array $headers The list of headers 132 * @return boolean 133 */ 134 function isVacationAutoResponder($headers) 135 { 136 // loop through the headers and make sure they are all lowercase. 137 foreach ($headers as $key => $value) { 138 $headers[strtolower($key)] = $value; 139 } 140 141 if ((@$headers['x-vacationmessage'] == 'Yes') || (@$headers['auto-submitted'] == 'auto-replied (vacation)')) { 142 return true; 143 } else { 144 return false; 145 } 146 } 147 148 149 /** 150 * Method used to parse a string and return all email addresses contained 151 * within it. 152 * 153 * @access public 154 * @param string $str The string containing email addresses 155 * @return array The list of email addresses 156 */ 157 function getEmailAddresses($str) 158 { 159 $str = Mail_API::fixAddressQuoting($str); 160 $str = Mime_Helper::encodeValue($str); 161 $structs = Mail_RFC822::parseAddressList($str); 162 $addresses = array(); 163 foreach ($structs as $structure) { 164 if ((!empty($structure->mailbox)) && (!empty($structure->host))) { 165 $addresses[] = $structure->mailbox . '@' . $structure->host; 166 } 167 } 168 return $addresses; 169 } 170 171 172 /** 173 * Method used to build a properly quoted email address, in the form of 174 * "Sender Name" <sender@example.com>. 175 * 176 * @access public 177 * @param string $address The email address value 178 * @return array The address information 179 */ 180 function fixAddressQuoting($address) 181 { 182 // split multiple addresses if needed 183 $addresses = Mail_API::splitAddresses($address); 184 185 $return = array(); 186 foreach ($addresses as $address) { 187 // check if we have a < 188 if ((strstr($address, '<')) && (!Mime_Helper::isQuotedPrintable($address))) { 189 $address = stripslashes(trim($address)); 190 // is the address in the format 'name' <address> ? 191 if ((strstr($address, "'")) || (strstr($address, "."))) { 192 $bracket_pos = strpos($address, '<'); 193 if ($bracket_pos != 0) { 194 $bracket_pos = $bracket_pos - 1; 195 } 196 $first_part = substr($address, 0, $bracket_pos); 197 if (!empty($first_part)) { 198 $first_part = '"' . str_replace('"', '\"', preg_replace("/(^\")|(\"$)/", '', $first_part)) . '"'; 199 } 200 $second_part = substr($address, strpos($address, '<')); 201 $address = $first_part . ' ' . $second_part; 202 // if the address was already in the format "'name'" <address>, then this code 203 // will end up adding even more double quotes, so let's remove any excess 204 $return[] = str_replace('""', '"', $address); 205 } else { 206 $return[] = $address; 207 } 208 } else { 209 $return[] = $address; 210 } 211 } 212 213 return join(',', $return); 214 } 215 216 217 /** 218 * Method used to break down the email address information and 219 * return it for easy manipulation. 220 * 221 * @access public 222 * @param string $address The email address value 223 * @param boolean $multiple If multiple addresses should be returned 224 * @return array The address information 225 */ 226 function getAddressInfo($address, $multiple = false) 227 { 228 $address = Mail_API::fixAddressQuoting($address); 229 $address = Mime_Helper::encodeValue($address); 230 $t = Mail_RFC822::parseAddressList($address, null, null, false); 231 if (PEAR::isError($t)) { 232 return $t; 233 } 234 if ($multiple) { 235 $returns = array(); 236 for ($i = 0; $i < count($t); $i++) { 237 $returns[] = array( 238 'sender_name' => $t[$i]->personal, 239 'email' => $t[$i]->mailbox . '@' . $t[$i]->host, 240 'username' => $t[$i]->mailbox, 241 'host' => $t[$i]->host 242 ); 243 } 244 return $returns; 245 } else { 246 return array( 247 'sender_name' => $t[0]->personal, 248 'email' => $t[0]->mailbox . '@' . $t[0]->host, 249 'username' => $t[0]->mailbox, 250 'host' => $t[0]->host 251 ); 252 } 253 } 254 255 256 /** 257 * Method used to get the email address portion of a given 258 * recipient information. 259 * 260 * @access public 261 * @param string $address The email address value 262 * @return string The email address 263 */ 264 function getEmailAddress($address) 265 { 266 $info = Mail_API::getAddressInfo($address); 267 if (PEAR::isError($info)) { 268 return $info; 269 } 270 return $info['email']; 271 } 272 273 274 /** 275 * Method used to get the name portion of a given recipient information. 276 * 277 * @access public 278 * @param string $address The email address value 279 * @param boolean $multiple If multiple addresses should be returned 280 * @return mixed The name or an array of names if multiple is true 281 */ 282 function getName($address, $multiple = false) 283 { 284 $info = Mail_API::getAddressInfo($address, true); 285 if (PEAR::isError($info)) { 286 return $info; 287 } 288 $returns = array(); 289 foreach ($info as $row) { 290 if (!empty($row['sender_name'])) { 291 if ((substr($row['sender_name'], 0, 1) == '"') && (substr($row['sender_name'], -1) == '"')) { 292 $row['sender_name'] = substr($row['sender_name'], 1, -1); 293 } 294 $returns[] = Mime_Helper::fixEncoding($row['sender_name']); 295 } else { 296 $returns[] = $row['email']; 297 } 298 } 299 if ($multiple) { 300 return $returns; 301 } else { 302 return $returns[0]; 303 } 304 } 305 306 307 /** 308 * Method used to get the formatted name of the passed address 309 * information. 310 * 311 * @access public 312 * @param string $name The name of the recipient 313 * @param string $email The email of the recipient 314 * @return string 315 */ 316 function getFormattedName($name, $email) 317 { 318 return $name . " <" . $email . ">"; 319 } 320 321 322 /** 323 * Method used to get the application specific settings regarding 324 * which SMTP server to use, such as login and server information. 325 * 326 * @access public 327 * @return array 328 */ 329 function getSMTPSettings() 330 { 331 $settings = Setup::load(); 332 settype($settings['smtp']['auth'], 'boolean'); 333 return $settings["smtp"]; 334 } 335 336 337 /** 338 * Method used to set the text version of the body of the MIME 339 * multipart message that you wish to send. 340 * 341 * @access public 342 * @param string $text The text-based message 343 * @return void 344 */ 345 function setTextBody($text) 346 { 347 $this->text_body = $text; 348 $this->mime->setTXTBody($text); 349 } 350 351 352 /** 353 * Method used to set the HTML version of the body of the MIME 354 * multipart message that you wish to send. 355 * 356 * @access public 357 * @param string $html The HTML-based message 358 * @return void 359 */ 360 function setHTMLBody($html) 361 { 362 $this->mime->setHTMLBody($html); 363 } 364 365 366 /** 367 * Method used to add an embedded image to a MIME message. 368 * 369 * @access public 370 * @param string $filename The full path to the image 371 * @return void 372 */ 373 function addHTMLImage($filename) 374 { 375 $this->mime->addHTMLImage($filename); 376 } 377 378 379 /** 380 * Method used to set extra headers that you may wish to use when 381 * sending the email. 382 * 383 * @access public 384 * @param mixed $header The header(s) to set 385 * @param mixed $value The value of the header to be set 386 * @return void 387 */ 388 function setHeaders($header, $value = FALSE) 389 { 390 if (is_array($header)) { 391 foreach ($header as $key => $value) { 392 $this->headers[$key] = Mime_Helper::encodeValue($value); 393 } 394 } else { 395 $this->headers[$header] = Mime_Helper::encodeValue($value); 396 } 397 } 398 399 400 /** 401 * Method used to add an email address in the Cc list. 402 * 403 * @access public 404 * @param string $email The email address to be added 405 * @return void 406 */ 407 function addCc($email) 408 { 409 $this->mime->addCc($email); 410 } 411 412 413 /** 414 * Method used to add an attachment to the message. 415 * 416 * @access public 417 * @param string $name The attachment name 418 * @param string $data The attachment data 419 * @param string $content_type The content type of the attachment 420 * @return void 421 */ 422 function addAttachment($name, $data, $content_type) 423 { 424 $this->mime->addAttachment($data, $content_type, $name, false); 425 } 426 427 428 /** 429 * Method used to add a message/rfc822 attachment to the message. 430 * 431 * @access public 432 * @param string $message_body The attachment data 433 * @return void 434 */ 435 function addMessageRfc822($message_body) 436 { 437 $this->mime->addMessageRfc822($message_body, '8bit'); 438 } 439 440 441 /** 442 * Removes the warning message contained in a message, so that certain users 443 * don't receive that extra information as it may not be relevant to them. 444 * 445 * @access public 446 * @param string $str The body of the email 447 * @return string The body of the email, without the warning message 448 */ 449 function stripWarningMessage($str) 450 { 451 $str = str_replace(Mail_API::getWarningMessage('allowed'), '', $str); 452 $str = str_replace(Mail_API::getWarningMessage('blocked'), '', $str); 453 return $str; 454 } 455 456 457 /** 458 * Returns the warning message that needs to be added to the top of routed 459 * issue emails to alert the recipient that he can (or not) send emails to 460 * the issue notification list. 461 * 462 * @access public 463 * @param string $type Whether the warning message is of an allowed recipient or not 464 * @return string The warning message 465 */ 466 function getWarningMessage($type) 467 { 468 if ($type == 'allowed') { 469 $str = ev_gettext("ADVISORY: Your reply will be sent to the notification list."); 470 } else { 471 $str = ev_gettext("WARNING: If replying, add yourself to Authorized Repliers list first."); 472 } 473 return $str; 474 } 475 476 477 /** 478 * Method used to add a customized warning message to the body 479 * of outgoing emails. 480 * 481 * @access public 482 * @param integer $issue_id The issue ID 483 * @param string $to The recipient of the message 484 * @param string $body The body of the message 485 * @param headers $headers The headers of the message 486 * @return string The body of the message with the warning message, if appropriate 487 */ 488 function addWarningMessage($issue_id, $to, $body, $headers) 489 { 490 $setup = Setup::load(); 491 if ((@$setup['email_routing']['status'] == 'enabled') && 492 ($setup['email_routing']['warning']['status'] == 'enabled')) { 493 // check if the recipient can send emails to the customer 494 $recipient_email = Mail_API::getEmailAddress($to); 495 $recipient_usr_id = User::getUserIDByEmail($recipient_email); 496 // don't add the warning message if the recipient is an unknown email address 497 if (empty($recipient_usr_id)) { 498 return $body; 499 } else { 500 // don't add anything if the recipient is a known customer contact 501 $recipient_role_id = User::getRoleByUser($recipient_usr_id, Issue::getProjectID($issue_id)); 502 if ($recipient_role_id == User::getRoleID('Customer')) { 503 return $body; 504 } else { 505 if (!Support::isAllowedToEmail($issue_id, $recipient_email)) { 506 $warning = Mail_API::getWarningMessage('blocked'); 507 } else { 508 $warning = Mail_API::getWarningMessage('allowed'); 509 } 510 if (@$headers['Content-Transfer-Encoding'] == 'base64') { 511 return base64_encode($warning . "\n\n" . trim(base64_decode($body))); 512 } else { 513 return $warning . "\n\n" . $body; 514 } 515 } 516 } 517 } else { 518 return $body; 519 } 520 } 521 522 523 /** 524 * Strips out email headers that should not be sent over to the recipient 525 * of the routed email. The 'Received:' header was sometimes being used to 526 * validate the sender of the message, and because of that some emails were 527 * not being delivered correctly. 528 * 529 * @access public 530 * @param string $headers The full headers of the email 531 * @return string The headers of the email, without the stripped ones 532 */ 533 function stripHeaders($headers) 534 { 535 $headers = preg_replace('/\r?\n([ \t])/', '$1', $headers); 536 $headers = preg_replace('/^(Received: .*\r?\n)/m', '', $headers); 537 // also remove the read-receipt header 538 $headers = preg_replace('/^(Disposition-Notification-To: .*\r?\n)/m', '', $headers); 539 return $headers; 540 } 541 542 543 /** 544 * Method used to send the SMTP based email message. 545 * 546 * @access public 547 * @param string $from The originator of the message 548 * @param string $to The recipient of the message 549 * @param string $subject The subject of the message 550 * @param integer $issue_id The ID of the issue. If false, email will not be associated with issue. 551 * @param string $type The type of message this is 552 * @param integer $sender_usr_id The id of the user sending this email. 553 * @param integer $type_id The ID of the event that triggered this notification (issue_id, sup_id, not_id, etc) 554 * @return string The full body of the message that was sent 555 */ 556 function send($from, $to, $subject, $save_email_copy = 0, $issue_id = false, $type = '', $sender_usr_id = false, $type_id = false) 557 { 558 static $support_levels; 559 560 // encode the addresses 561 $from = MIME_Helper::encodeAddress($from); 562 $to = MIME_Helper::encodeAddress($to); 563 $subject = MIME_Helper::encode($subject); 564 565 $body = $this->mime->get(array('text_charset' => APP_CHARSET, 'head_charset' => APP_CHARSET, 'text_encoding' => APP_EMAIL_ENCODING)); 566 $headers = array( 567 'From' => $from, 568 'To' => Mail_API::fixAddressQuoting($to), 569 'Subject' => $subject 570 ); 571 572 $this->setHeaders($headers); 573 $hdrs = $this->mime->headers($this->headers); 574 $res = Mail_Queue::add($to, $hdrs, $body, $save_email_copy, $issue_id, $type, $sender_usr_id, $type_id); 575 if ((PEAR::isError($res)) || ($res == false)) { 576 return $res; 577 } else { 578 // RFC 822 formatted date 579 $header = 'Date: ' . date('D, j M Y H:i:s O') . "\r\n"; 580 // return the full dump of the email 581 foreach ($hdrs as $name => $value) { 582 $header .= "$name: $value\r\n"; 583 } 584 $header .= "\r\n"; 585 return $header . $body; 586 } 587 } 588 589 590 /** 591 * Returns the full headers for the email properly encoded. 592 * 593 * @access public 594 * @param string $from The sender of the email 595 * @param string $to The recipient of the email 596 * @param string $subject The subject of this email 597 * @return string The full header version of the email 598 */ 599 function getFullHeaders($from, $to, $subject) 600 { 601 // encode the addresses 602 $from = MIME_Helper::encodeAddress($from); 603 $to = MIME_Helper::encodeAddress($to); 604 $subject = MIME_Helper::encode($subject); 605 606 $body = $this->mime->get(array('text_charset' => APP_CHARSET, 'head_charset' => APP_CHARSET, 'text_encoding' => APP_EMAIL_ENCODING)); 607 $this->setHeaders(array( 608 'From' => $from, 609 'To' => $to, 610 'Subject' => $subject 611 )); 612 $hdrs = $this->mime->headers($this->headers); 613 // RFC 822 formatted date 614 $header = 'Date: ' . gmdate('D, j M Y H:i:s O') . "\r\n"; 615 // return the full dump of the email 616 foreach ($hdrs as $name => $value) { 617 $header .= "$name: $value\r\n"; 618 } 619 $header .= "\r\n"; 620 return $header . $body; 621 } 622 623 624 /** 625 * Method used to save a copy of the given email to a configurable address. 626 * 627 * @access public 628 * @param array $email The email to save. 629 */ 630 function saveEmailInformation($email) 631 { 632 static $subjects; 633 634 $hdrs = $email['headers']; 635 $body = $email['body']; 636 $issue_id = $email['maq_iss_id']; 637 $sender_usr_id = $email['maq_usr_id']; 638 639 // do we really want to save every outgoing email? 640 $setup = Setup::load(); 641 if ((@$setup['smtp']['save_outgoing_email'] != 'yes') || (empty($setup['smtp']['save_address']))) { 642 return false; 643 } 644 645 // ok, now parse the headers text and build the assoc array 646 $full_email = $hdrs . "\n\n" . $body; 647 $structure = Mime_Helper::decode($full_email, FALSE, FALSE); 648 $_headers =& $structure->headers; 649 $header_names = Mime_Helper::getHeaderNames($hdrs); 650 $headers = array(); 651 foreach ($_headers as $lowercase_name => $value) { 652 $headers[$header_names[$lowercase_name]] = $value; 653 } 654 // remove any Reply-To:/Return-Path: values from outgoing messages 655 unset($headers['Reply-To']); 656 unset($headers['Return-Path']); 657 658 // prevent duplicate emails from being sent out... 659 $subject = @$headers['Subject']; 660 if (@in_array($subject, $subjects)) { 661 return false; 662 } 663 664 // replace the To: header with the requested address 665 $address = $setup['smtp']['save_address']; 666 $headers['To'] = $address; 667 668 // add specialized headers if they are not already added 669 if (empty($headers['X-Eventum-Type'])) { 670 $headers += Mail_API::getSpecializedHeaders($issue_id, $email['maq_type'], $headers, $sender_usr_id); 671 } 672 673 $params = Mail_API::getSMTPSettings($address); 674 $mail =& Mail::factory('smtp', $params); 675 $res = $mail->send($address, $headers, $body); 676 if (PEAR::isError($res)) { 677 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 678 } 679 680 $subjects[] = $subject; 681 } 682 683 684 /** 685 * Since Mail::prepareHeaders() is not supposed to be called statically, this method 686 * instantiates an instance of the mail class and calls prepareHeaders on it. 687 * 688 * @param array $headers The array of headers to prepare, in an associative 689 * array, where the array key is the header name (ie, 690 * 'Subject'), and the array value is the header 691 * value (ie, 'test'). The header produced from those 692 * values would be 'Subject: test'. 693 * @return mixed Returns false if it encounters a bad address, 694 * otherwise returns an array containing two 695 * elements: Any From: address found in the headers, 696 * and the plain text version of the headers. 697 */ 698 function prepareHeaders($headers) 699 { 700 $params = Mail_API::getSMTPSettings(); 701 $mail =& Mail::factory('smtp', $params); 702 return $mail->prepareHeaders($headers); 703 } 704 705 706 /** 707 * Generates the specialized headers for an email. 708 * 709 * @access public 710 * @param integer $issue_id The issue ID 711 * @param string $type The type of message this is 712 * @param string $headers The existing headers of this message. 713 * @param integer $sender_usr_id The id of the user sending this email. 714 * @return array An array of specialized headers 715 */ 716 function getSpecializedHeaders($issue_id, $type, $headers, $sender_usr_id) 717 { 718 $new_headers = array(); 719 if (!empty($issue_id)) { 720 $prj_id = Issue::getProjectID($issue_id); 721 if (count(Group::getAssocList($prj_id)) > 0) { 722 // group issue is currently assigned too 723 $new_headers['X-Eventum-Group-Issue'] = Group::getName(Issue::getGroupID($issue_id)); 724 725 // group of whoever is sending this message. 726 if (empty($sender_usr_id)) { 727 $new_headers['X-Eventum-Group-Replier'] = $new_headers['X-Eventum-Group-Issue']; 728 } else { 729 $new_headers['X-Eventum-Group-Replier'] = Group::getName(User::getGroupID($sender_usr_id)); 730 } 731 732 // group of current assignee 733 $assignees = Issue::getAssignedUserIDs($issue_id); 734 if (empty($assignees[0])) { 735 $new_headers['X-Eventum-Group-Assignee'] = ''; 736 } else { 737 $new_headers['X-Eventum-Group-Assignee'] = @Group::getName(User::getGroupID($assignees[0])); 738 } 739 } 740 if (Customer::hasCustomerIntegration($prj_id)) { 741 if (empty($support_levels)) { 742 $support_levels = Customer::getSupportLevelAssocList($prj_id); 743 } 744 $customer_id = Issue::getCustomerID($issue_id); 745 $contract_id = Issue::getContractID($issue_id); 746 if (!empty($customer_id)) { 747 $customer_details = Customer::getDetails($prj_id, $customer_id, false, $contract_id); 748 $new_headers['X-Eventum-Customer'] = $customer_details['customer_name']; 749 } 750 if (count($support_levels) > 0) { 751 $new_headers['X-Eventum-Level'] = $support_levels[Customer::getSupportLevelID($prj_id, $customer_id, $contract_id)]; 752 } 753 } 754 // add assignee header 755 $new_headers['X-Eventum-Assignee'] = join(',', User::getEmail(Issue::getAssignedUserIDs($issue_id))); 756 757 $new_headers['X-Eventum-Category'] = Category::getTitle(Issue::getCategory($issue_id)); 758 $new_headers['X-Eventum-Project'] = Project::getName($prj_id); 759 } 760 $new_headers['X-Eventum-Type'] = $type; 761 return $new_headers; 762 } 763 764 765 /** 766 * Method used to get the appropriate Message-ID header for a 767 * given issue. 768 * 769 * @access public 770 * @return string The Message-ID header 771 */ 772 function generateMessageID() 773 { 774 list($usec, $sec) = explode(" ", microtime()); 775 $time = ((float)$usec + (float)$sec); 776 $first = base_convert($time, 10, 36); 777 mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff); 778 $rand = mt_rand(); 779 $second = base_convert($rand, 10, 36); 780 return "<eventum." . $first . "." . $second . "@" . APP_HOSTNAME . ">"; 781 } 782 783 784 /** 785 * Returns the referenced message-id for a given reply. 786 * 787 * @access public 788 * @param string $text_headers The full headers of the reply 789 * @return string The message-id of the original email 790 */ 791 function getReferenceMessageID($text_headers) 792 { 793 $references = array(); 794 if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers, $matches)) { 795 return trim($matches[1]); 796 } 797 if (preg_match('/^References: (.+?)(\r?\n\r?\n|\r?\n\r?\S)/smi', $text_headers, $matches)) { 798 $references = explode(" ", Mail_API::unfold(trim($matches[1]))); 799 $references = array_map('trim', $references); 800 // return the first message-id in the list of references 801 return $references[0]; 802 } 803 return ''; 804 } 805 806 807 /** 808 * Returns the message IDs of all emails this message references. 809 * 810 * @access public 811 * @param string $text_headers The full headers of the message 812 * @return array An array of message-ids 813 */ 814 function getAllReferences($text_headers) 815 { 816 $references = array(); 817 if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers, $matches)) { 818 $references[] = trim($matches[1]); 819 } 820 if (preg_match('/^References: (.+?)(\r?\n\r?\n|\r?\n\r?\S)/smi', $text_headers, $matches)) { 821 $references = array_merge($references, explode(" ", Mail_API::unfold(trim($matches[1])))); 822 $references = array_map('trim', $references); 823 $references = array_unique($references); 824 } 825 foreach ($references as $key => $reference) { 826 if (empty($reference)) { 827 unset($references[$key]); 828 } 829 } 830 return $references; 831 } 832 833 834 /** 835 * Checks to make sure In-Reply-To and References headers are correct. 836 * 837 */ 838 function rewriteThreadingHeaders($issue_id, $full_email, $headers, $type = 'email') 839 { 840 list($text_headers, $body) = Mime_Helper::splitHeaderBody($full_email); 841 842 if ($type == 'note') { 843 $class = 'Note'; 844 } else { 845 $class = 'Support'; 846 } 847 848 $msg_id = Mail_API::getMessageID($text_headers, $body); 849 850 // check if the In-Reply-To header exists and if so, does it relate to a message stored in Eventum 851 // if it does not, set new In-Reply-To header 852 $reference_msg_id = Mail_API::getReferenceMessageID($text_headers); 853 $reference_issue_id = false; 854 if (!empty($reference_msg_id)) { 855 // check if referenced msg id is associated with this issue 856 $reference_issue_id = call_user_func(array($class, 'getIssueByMessageID'), $reference_msg_id); 857 } 858 859 if ((empty($reference_msg_id)) || ($reference_issue_id != $issue_id)) { 860 $reference_msg_id = Issue::getRootMessageID($issue_id); 861 } 862 $references = Mail_API::getReferences($issue_id, $reference_msg_id, $type); 863 864 // now the fun part, re-writing the email headers 865 if (empty($headers['message-id'])) { 866 // add Message-ID since it doesn't exist (curses on Outlook 2003) 867 $text_headers .= "\r\nMessage-ID: $msg_id"; 868 $headers['message-id'] = $msg_id; 869 } 870 871 if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers) > 0) { 872 // replace existing header 873 $text_headers = preg_replace('/^In-Reply-To: (.*)/mi', 'In-Reply-To: ' . $reference_msg_id, $text_headers, 1); 874 } else { 875 // add new header after message ID 876 $text_headers = preg_replace('/^Message-ID: (.*)$/mi', "Message-ID: $1\r\nIn-Reply-To: $reference_msg_id", $text_headers, 1); 877 } 878 $headers['in-reply-to'] = $reference_msg_id; 879 if (preg_match('/^References: (.*)/mi', $text_headers) > 0) { 880 // replace existing header 881 $text_headers = preg_replace('/^References: (.*)/mi', 'References: ' . Mail_API::fold(join(' ', $references)), $text_headers, 1); 882 } else { 883 // add new header after In-Reply-To 884 $text_headers = preg_replace('/^In-Reply-To: (.*)$/mi', "In-Reply-To: $1\r\nReferences: " . Mail_API::fold(join(' ', $references)), $text_headers, 1); 885 } 886 $headers['references'] = Mail_API::fold(join(' ', $references)); 887 return array($text_headers . "\r\n\r\n" . $body, $headers); 888 } 889 890 891 /** 892 * Returns a complete list of references for an email/note, including 893 * the issue root message ID 894 * 895 * @access private 896 * @param integer $issue_id The ID of the issue 897 * @param string $msg_id The ID of the message 898 * @param string $type If this is a note or an email 899 * @return array An array of message IDs 900 */ 901 function getReferences($issue_id, $msg_id, $type) 902 { 903 $references = array(); 904 Mail_API::_getReferences($msg_id, $type, $references); 905 $references[] = Issue::getRootMessageID($issue_id); 906 $references = array_reverse(array_unique($references)); 907 return $references; 908 } 909 910 911 912 /** 913 * Method to get the list of messages an email/note references 914 * 915 * @access private 916 * @param string $msg_id The ID of the parent message 917 * @param string $type If this is a note or an email 918 * @param array $references The array the references will be stored in. 919 */ 920 function _getReferences($msg_id, $type, &$references) 921 { 922 $references[] = $msg_id; 923 if ($type == 'note') { 924 $class = 'Note'; 925 } else { 926 $class = 'Support'; 927 } 928 $parent_msg_id = call_user_func(array($class, 'getParentMessageIDbyMessageID'), $msg_id); 929 if (!empty($parent_msg_id)) { 930 Mail_API::_getReferences($parent_msg_id, $type, $references); 931 } 932 } 933 934 935 936 function getBaseThreadingHeaders($issue_id) 937 { 938 $root_msg_id = Issue::getRootMessageID($issue_id); 939 return array( 940 "Message-ID" => Mail_API::generateMessageID(), 941 "In-Reply-To" => $root_msg_id, 942 "References" => $root_msg_id 943 ); 944 } 945 946 /** 947 * Unfolds message headers 948 * 949 * @access public 950 * @param string $input The headers to unfold 951 * @return string The unfolded headers 952 */ 953 function unfold($input) 954 { 955 $input = preg_replace("/\r?\n/", "\r\n", $input); 956 $input = preg_replace("/\r\n(\t| )+/", ' ', $input); 957 return $input; 958 } 959 960 /** 961 * Folds message headers 962 * 963 * @access public 964 * @param string $input The headers to fold 965 * @return string The folded headers 966 */ 967 function fold($input) 968 { 969 return wordwrap($input, 70, "\r\n "); 970 } 971 972 973 /** 974 * Returns the Message-ID from an email. If no message ID is found (Outlook 2003 doesn't 975 * generate them in some cases) a "fake" message-id will be calculated. 976 * 977 * @access public 978 * @param string $headers The message headers 979 * @param string $body The message body 980 */ 981 function getMessageID($headers, $body) 982 { 983 // try to parse out actual message-id header 984 if (preg_match('/^Message-ID: (.*)/mi', $headers, $matches)) { 985 return trim($matches[1]); 986 } else { 987 // no match, calculate hash to make fake message ID 988 $first = base_convert(md5($headers), 10, 36); 989 $second = base_convert(md5($body), 10, 36); 990 return "<eventum.md5." . $first . "." . $second . "@" . APP_HOSTNAME . ">"; 991 } 992 993 } 994 995 996 function splitAddresses($addresses) 997 { 998 $mail = new Mail_RFC822($addresses); 999 1000 $mail->parseAddressList(); 1001 1002 $return = array(); 1003 if (is_array($mail->addresses)) { 1004 foreach ($mail->addresses as $address) { 1005 $return[] = $address['address']; 1006 } 1007 } 1008 return $return; 1009 } 1010 } 1011 1012 // benchmarking the included file (aka setup time) 1013 if (APP_BENCHMARK) { 1014 $GLOBALS['bench']->setMarker('Included Mail_API Class'); 1015 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Dec 19 21:21:33 2007 | Cross-referenced by PHPXref 0.7 |