[ Index ]

PHP Cross Reference of Eventum

title

Body

[close]

/include/ -> class.mail.php (source)

   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  }


Generated: Wed Dec 19 21:21:33 2007 Cross-referenced by PHPXref 0.7