PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/whups/lib/Mail.php

https://github.com/wrobel/horde
PHP | 264 lines | 179 code | 22 blank | 63 comment | 42 complexity | 54448bc52199de038bf0521bce92e659 MD5 | raw file
Possible License(s): BSD-2-Clause, AGPL-1.0, LGPL-2.1, LGPL-3.0, BSD-3-Clause, LGPL-2.0, GPL-2.0
  1. <?php
  2. /**
  3. * Whups mail processing library.
  4. *
  5. * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
  6. *
  7. * See the enclosed file LICENSE for license information (BSD). If you
  8. * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
  9. *
  10. * @author Jason M. Felice <jason.m.felice@gmail.com>
  11. * @author Jan Schneider <jan@horde.org>
  12. * @package Whups
  13. */
  14. class Whups_Mail
  15. {
  16. /**
  17. * Parse a MIME message and create a new ticket.
  18. *
  19. * @param string $text This is the full text of the MIME message.
  20. * @param array $info An array of information for the new ticket.
  21. * This should include:
  22. * - 'queue' => queue id
  23. * - 'type' => type id
  24. * - 'state' => state id
  25. * - 'priority' => priority id
  26. * - 'ticket' => ticket id (prevents creation
  27. * of new tickets)
  28. * @param string $auth_user This will be the Horde user that creates the
  29. * ticket. If null, we will try to deduce from
  30. * the message's From: header. We do NOT default
  31. * to $GLOBALS['registry']->getAuth().
  32. *
  33. * @return Whups_Ticket Ticket.
  34. */
  35. static public function processMail($text, array $info, $auth_user = null)
  36. {
  37. global $conf;
  38. $message = Horde_Mime_Part::parseMessage($text);
  39. if (preg_match("/^(.*?)\r?\n\r?\n/s", $text, $matches)) {
  40. $hdrText = $matches[1];
  41. } else {
  42. $hdrText = $text;
  43. }
  44. $headers = Horde_Mime_Headers::parseHeaders($hdrText);
  45. // If this message was generated by Whups, don't process it.
  46. if ($headers->getValue('X-Whups-Generated')) {
  47. return true;
  48. }
  49. // Try to avoid bounces.
  50. $from = $headers->getValue('from');
  51. if (strpos($headers->getValue('Content-Type'), 'multipart/report') !== false ||
  52. stripos($from, 'mailer-daemon@') !== false ||
  53. stripos($from, 'postmaster@') !== false ||
  54. !is_null($headers->getValue('X-Failed-Recipients'))) {
  55. return true;
  56. }
  57. // Use the message subject as the ticket summary.
  58. $info['summary'] = trim($headers->getValue('subject'));
  59. if (empty($info['summary'])) {
  60. $info['summary'] = _("[No Subject]");
  61. }
  62. // Format the message into a comment.
  63. $comment = _("Received message:") . "\n\n";
  64. if (!empty($GLOBALS['conf']['mail']['include_headers'])) {
  65. foreach ($headers->toArray(array('nowrap' => true)) as $name => $vals) {
  66. if (!in_array(strtolower($name), array('subject', 'from', 'to', 'cc', 'date'))) {
  67. if (is_array($vals)) {
  68. foreach ($vals as $val) {
  69. $comment .= $name . ': ' . $val . "\n";
  70. }
  71. } else {
  72. $comment .= $name . ': ' . $vals . "\n";
  73. }
  74. }
  75. }
  76. $comment .= "\n";
  77. }
  78. // Look for the body part.
  79. $body_id = $message->findBody();
  80. if ($body_id) {
  81. $part = $message->getPart($body_id);
  82. $content = Horde_String::convertCharset(
  83. $part->getContents(), $part->getCharset(), 'UTF-8');
  84. switch ($part->getType()) {
  85. case 'text/plain':
  86. $comment .= $content;
  87. break;
  88. case 'text/html':
  89. $comment .= Horde_Text_Filter::filter(
  90. $content, array('Html2text'), array(array('width' => 0)));;
  91. break;
  92. default:
  93. $comment .= _("[ Could not render body of message. ]");
  94. break;
  95. }
  96. } else {
  97. $comment .= _("[ Could not render body of message. ]");
  98. }
  99. $info['comment'] = $comment . "\n";
  100. // Try to determine the Horde user for creating the ticket.
  101. if (empty($auth_user)) {
  102. $auth_user = self::_findAuthUser(Horde_Mime_Address::bareAddress($from));
  103. }
  104. $author = $auth_user;
  105. if (empty($auth_user) && !empty($info['default_auth'])) {
  106. $auth_user = $info['default_auth'];
  107. if (!empty($from)) {
  108. $info['user_email'] = $from;
  109. }
  110. }
  111. if (empty($auth_user) && !empty($conf['mail']['username'])) {
  112. $auth_user = $conf['mail']['username'];
  113. if (!empty($from)) {
  114. $info['user_email'] = $from;
  115. }
  116. }
  117. // Authenticate as the correct Horde user.
  118. if (!empty($auth_user) && $auth_user != $GLOBALS['registry']->getAuth()) {
  119. $GLOBALS['registry']->setAuth($auth_user, array());
  120. }
  121. // Extract attachments.
  122. $attachments = array();
  123. $dl_list = array_slice(array_keys($message->contentTypeMap()), 1);
  124. foreach ($dl_list as $key) {
  125. $part = $message->getPart($key);
  126. if (($key == $body_id && $part->getType() == 'text/plain') ||
  127. $part->getType() == 'multipart/alternative' ||
  128. $part->getType() == 'multipart/mixed') {
  129. continue;
  130. }
  131. $tmp_name = Horde::getTempFile('whups');
  132. $fp = @fopen($tmp_name, 'wb');
  133. if (!$fp) {
  134. Horde::logMessage(sprintf('Cannot open file %s for writing.',
  135. $tmp_name), 'ERR');
  136. return $ticket;
  137. }
  138. fwrite($fp, $part->getContents());
  139. fclose($fp);
  140. $part_name = $part->getName(true);
  141. if (!$part_name) {
  142. $ptype = $part->getPrimaryType();
  143. switch ($ptype) {
  144. case 'multipart':
  145. case 'application':
  146. $part_name = sprintf(_("%s part"), ucfirst($part->getSubType()));
  147. break;
  148. default:
  149. $part_name = sprintf(_("%s part"), ucfirst($ptype));
  150. break;
  151. }
  152. if ($ext = Horde_Mime_Magic::mimeToExt($part->getType())) {
  153. $part_name .= '.' . $ext;
  154. }
  155. }
  156. $attachments[] = array(
  157. 'name' => $part_name,
  158. 'tmp_name' => $tmp_name);
  159. }
  160. // See if we can match this message to an existing ticket.
  161. if ($ticket = self::_findTicket($info)) {
  162. $ticket->change('comment', $info['comment']);
  163. $ticket->change('comment-email', $from);
  164. if ($attachments) {
  165. $ticket->change('attachments', $attachments);
  166. }
  167. $ticket->commit($author);
  168. } elseif (!empty($info['ticket'])) {
  169. // Didn't match an existing ticket though a ticket number had been
  170. // specified.
  171. throw new Whups_Exception(
  172. sprintf(_("Could not find ticket \"%s\"."), $info['ticket']));
  173. } else {
  174. if (!empty($info['guess-queue'])) {
  175. // Try to guess the queue name for the new ticket from the
  176. // message subject.
  177. $queues = $GLOBALS['whups_driver']->getQueues();
  178. foreach ($queues as $queueId => $queueName) {
  179. if (preg_match('/\b' . preg_quote($queueName, '/') . '\b/i',
  180. $info['summary'])) {
  181. $info['queue'] = $queueId;
  182. break;
  183. }
  184. }
  185. }
  186. $info['attachments'] = $attachments;
  187. // Create a new ticket.
  188. $ticket = Whups_Ticket::newTicket($info, $author);
  189. }
  190. }
  191. /**
  192. * Returns the ticket number matching the provided information.
  193. *
  194. * @param array $info A hash with ticket information.
  195. *
  196. * @return integer The ticket number if has been passed in the subject,
  197. * false otherwise.
  198. */
  199. static protected function _findTicket(array $info)
  200. {
  201. if (!empty($info['ticket'])) {
  202. $ticketnum = $info['ticket'];
  203. } elseif (preg_match('/\[[\w\s]*#(\d+)\]/', $info['summary'], $matches)) {
  204. $ticketnum = $matches[1];
  205. } else {
  206. return false;
  207. }
  208. try {
  209. return Whups_Ticket::makeTicket($ticketnum);
  210. } catch (Whups_Exception $e) {
  211. return false;
  212. }
  213. }
  214. /**
  215. * Searches the From: header for an email address contained in one
  216. * of our users' identities.
  217. *
  218. * @param string $from The From address.
  219. *
  220. * @return string The Horde user name that matches the headers' From:
  221. * address or null if the users can't be listed or no
  222. * match has been found.
  223. */
  224. static protected function _findAuthUser($from)
  225. {
  226. $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
  227. if ($auth->hasCapability('list')) {
  228. foreach ($auth->listUsers() as $user) {
  229. $identity = $GLOBALS['injector']
  230. ->getInstance('Horde_Core_Factory_Identity')
  231. ->create($user);
  232. $addrs = $identity->getAll('from_addr');
  233. foreach ($addrs as $addr) {
  234. if (strcasecmp($from, $addr) == 0) {
  235. return $user;
  236. }
  237. }
  238. }
  239. }
  240. return false;
  241. }
  242. }