Overview

Packages

  • Horde
    • Form
    • MIME
      • Viewer
    • Scheduler
  • None
  • Whups
    • UnitTests

Classes

  • Horde_Core_Ui_VarRenderer_whups
  • Whups
  • Whups_Ajax_Imple_ContactAutoCompleter
  • Whups_Api
  • Whups_Driver
  • Whups_Driver_Sql
  • Whups_Form_AddComment
  • Whups_Form_Admin_AddAttribute
  • Whups_Form_Admin_AddPriority
  • Whups_Form_Admin_AddQueue
  • Whups_Form_Admin_AddReply
  • Whups_Form_Admin_AddState
  • Whups_Form_Admin_AddType
  • Whups_Form_Admin_AddUser
  • Whups_Form_Admin_AddVersion
  • Whups_Form_Admin_CloneType
  • Whups_Form_Admin_DefaultPriority
  • Whups_Form_Admin_DefaultState
  • Whups_Form_Admin_DeleteAttribute
  • Whups_Form_Admin_DeletePriority
  • Whups_Form_Admin_DeleteQueue
  • Whups_Form_Admin_DeleteReply
  • Whups_Form_Admin_DeleteState
  • Whups_Form_Admin_DeleteType
  • Whups_Form_Admin_DeleteVersion
  • Whups_Form_Admin_EditAttributeStepOne
  • Whups_Form_Admin_EditAttributeStepTwo
  • Whups_Form_Admin_EditPriorityStepOne
  • Whups_Form_Admin_EditPriorityStepTwo
  • Whups_Form_Admin_EditQueueStepOne
  • Whups_Form_Admin_EditQueueStepTwo
  • Whups_Form_Admin_EditReplyStepOne
  • Whups_Form_Admin_EditReplyStepTwo
  • Whups_Form_Admin_EditStateStepOne
  • Whups_Form_Admin_EditStateStepTwo
  • Whups_Form_Admin_EditTypeStepOne
  • Whups_Form_Admin_EditTypeStepTwo
  • Whups_Form_Admin_EditUser
  • Whups_Form_Admin_EditVersionStepOne
  • Whups_Form_Admin_EditVersionStepTwo
  • Whups_Form_InsertBranch
  • Whups_Form_Query_AttributeCriterion
  • Whups_Form_Query_ChooseNameForLoad
  • Whups_Form_Query_ChooseNameForSave
  • Whups_Form_Query_DateCriterion
  • Whups_Form_Query_Delete
  • Whups_Form_Query_GroupCriterion
  • Whups_Form_Query_Parameter
  • Whups_Form_Query_PropertyCriterion
  • Whups_Form_Query_TextCriterion
  • Whups_Form_Query_UserCriterion
  • Whups_Form_Renderer_Comment
  • Whups_Form_Search
  • Whups_Form_SendReminder
  • Whups_Form_Ticket_CreateStepFour
  • Whups_Form_Ticket_CreateStepOne
  • Whups_Form_Ticket_CreateStepThree
  • Whups_Form_Ticket_CreateStepTwo
  • Whups_Form_Ticket_Edit
  • Whups_Form_TicketDetails
  • Whups_LoginTasks_SystemTask_Upgrade
  • Whups_Mail
  • Whups_Query
  • Whups_Query_Manager
  • Whups_Reports
  • Whups_Ticket
  • Whups_View_Base
  • Whups_View_Results
  • Whups_View_SavedQueries
  • Overview
  • Package
  • Class
  • Tree
  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: 
 39:         $message = Horde_Mime_Part::parseMessage($text);
 40: 
 41:         if (preg_match("/^(.*?)\r?\n\r?\n/s", $text, $matches)) {
 42:             $hdrText = $matches[1];
 43:         } else {
 44:             $hdrText = $text;
 45:         }
 46:         $headers = Horde_Mime_Headers::parseHeaders($hdrText);
 47: 
 48:         // If this message was generated by Whups, don't process it.
 49:         if ($headers->getValue('X-Whups-Generated')) {
 50:             return true;
 51:         }
 52: 
 53:         // Try to avoid bounces.
 54:         $from = $headers->getValue('from');
 55:         if (strpos($headers->getValue('Content-Type'), 'multipart/report') !== false ||
 56:             stripos($from, 'mailer-daemon@') !== false ||
 57:             stripos($from, 'postmaster@') !== false ||
 58:             !is_null($headers->getValue('X-Failed-Recipients'))) {
 59:             return true;
 60:         }
 61: 
 62:         // Use the message subject as the ticket summary.
 63:         $info['summary'] = trim($headers->getValue('subject'));
 64:         if (empty($info['summary'])) {
 65:             $info['summary'] = _("[No Subject]");
 66:         }
 67: 
 68:         // Format the message into a comment.
 69:         $comment = _("Received message:") . "\n\n";
 70:         if (!empty($GLOBALS['conf']['mail']['include_headers'])) {
 71:             foreach ($headers->toArray(array('nowrap' => true)) as $name => $vals) {
 72:                 if (!in_array(strtolower($name), array('subject', 'from', 'to', 'cc', 'date'))) {
 73:                     if (is_array($vals)) {
 74:                         foreach ($vals as $val) {
 75:                             $comment .= $name . ': ' . $val . "\n";
 76:                         }
 77:                     } else {
 78:                         $comment .= $name . ': ' . $vals . "\n";
 79:                     }
 80:                 }
 81:             }
 82: 
 83:             $comment .= "\n";
 84:         }
 85: 
 86:         // Look for the body part.
 87:         $body_id = $message->findBody();
 88:         if ($body_id) {
 89:             $part = $message->getPart($body_id);
 90:             $content = Horde_String::convertCharset(
 91:                 $part->getContents(), $part->getCharset(), 'UTF-8');
 92:             switch ($part->getType()) {
 93:             case 'text/plain':
 94:                 $comment .= $content;
 95:                 break;
 96:             case 'text/html':
 97:                 $comment .= Horde_Text_Filter::filter(
 98:                     $content, array('Html2text'), array(array('width' => 0)));;
 99:                 break;
100:             default:
101:                 $comment .= _("[ Could not render body of message. ]");
102:                 break;
103:             }
104:         } else {
105:             $comment .= _("[ Could not render body of message. ]");
106:         }
107: 
108:         $info['comment'] = $comment . "\n";
109: 
110:         // Try to determine the Horde user for creating the ticket.
111:         if (empty($auth_user)) {
112:             $auth_user = self::_findAuthUser(Horde_Mime_Address::bareAddress($from));
113:         }
114:         $author = $auth_user;
115: 
116:         if (empty($auth_user) && !empty($info['default_auth'])) {
117:             $auth_user = $info['default_auth'];
118:             if (!empty($from)) {
119:                 $info['user_email'] = $from;
120:             }
121:         }
122: 
123:         if (empty($auth_user) && !empty($conf['mail']['username'])) {
124:             $auth_user = $conf['mail']['username'];
125:             if (!empty($from)) {
126:                 $info['user_email'] = $from;
127:             }
128:         }
129: 
130:         // Authenticate as the correct Horde user.
131:         if (!empty($auth_user) && $auth_user != $GLOBALS['registry']->getAuth()) {
132:             $GLOBALS['registry']->setAuth($auth_user, array());
133:         }
134: 
135:         // Extract attachments.
136:         $attachments = array();
137:         $dl_list = array_slice(array_keys($message->contentTypeMap()), 1);
138:         foreach ($dl_list as $key) {
139:             $part = $message->getPart($key);
140:             if (($key == $body_id && $part->getType() == 'text/plain') ||
141:                 $part->getType() == 'multipart/alternative' ||
142:                 $part->getType() == 'multipart/mixed') {
143:                 continue;
144:             }
145:             $tmp_name = Horde::getTempFile('whups');
146:             $fp = @fopen($tmp_name, 'wb');
147:             if (!$fp) {
148:                 Horde::logMessage(sprintf('Cannot open file %s for writing.',
149:                                           $tmp_name), 'ERR');
150:                 return $ticket;
151:             }
152:             fwrite($fp, $part->getContents());
153:             fclose($fp);
154:             $part_name = $part->getName(true);
155:             if (!$part_name) {
156:                 $ptype = $part->getPrimaryType();
157:                 switch ($ptype) {
158:                 case 'multipart':
159:                 case 'application':
160:                     $part_name = sprintf(_("%s part"), ucfirst($part->getSubType()));
161:                     break;
162:                 default:
163:                     $part_name = sprintf(_("%s part"), ucfirst($ptype));
164:                     break;
165:                 }
166:                 if ($ext = Horde_Mime_Magic::mimeToExt($part->getType())) {
167:                     $part_name .= '.' . $ext;
168:                 }
169:             }
170:             $attachments[] = array(
171:                 'name' => $part_name,
172:                 'tmp_name' => $tmp_name);
173:         }
174: 
175:         // See if we can match this message to an existing ticket.
176:         if ($ticket = self::_findTicket($info)) {
177:             $ticket->change('comment', $info['comment']);
178:             $ticket->change('comment-email', $from);
179:             if ($attachments) {
180:                 $ticket->change('attachments', $attachments);
181:             }
182:             $ticket->commit($author);
183:         } elseif (!empty($info['ticket'])) {
184:             // Didn't match an existing ticket though a ticket number had been
185:             // specified.
186:             throw new Whups_Exception(
187:                 sprintf(_("Could not find ticket \"%s\"."), $info['ticket']));
188:         } else {
189:             if (!empty($info['guess-queue'])) {
190:                 // Try to guess the queue name for the new ticket from the
191:                 // message subject.
192:                 $queues = $GLOBALS['whups_driver']->getQueues();
193:                 foreach ($queues as $queueId => $queueName) {
194:                     if (preg_match('/\b' . preg_quote($queueName, '/') . '\b/i',
195:                                    $info['summary'])) {
196:                         $info['queue'] = $queueId;
197:                         break;
198:                     }
199:                 }
200:             }
201:             $info['attachments'] = $attachments;
202: 
203:             // Create a new ticket.
204:             $ticket = Whups_Ticket::newTicket($info, $author);
205:         }
206:     }
207: 
208:     /**
209:      * Returns the ticket number matching the provided information.
210:      *
211:      * @param array $info  A hash with ticket information.
212:      *
213:      * @return integer  The ticket number if has been passed in the subject,
214:      *                  false otherwise.
215:      */
216:     static protected function _findTicket(array $info)
217:     {
218:         if (!empty($info['ticket'])) {
219:             $ticketnum = $info['ticket'];
220:         } elseif (preg_match('/\[[\w\s]*#(\d+)\]/', $info['summary'], $matches)) {
221:             $ticketnum = $matches[1];
222:         } else {
223:             return false;
224:         }
225: 
226:         try {
227:             return Whups_Ticket::makeTicket($ticketnum);
228:         } catch (Whups_Exception $e) {
229:             return false;
230:         }
231:     }
232: 
233:     /**
234:      * Searches the From: header for an email address contained in one
235:      * of our users' identities.
236:      *
237:      * @param string $from  The From address.
238:      *
239:      * @return string  The Horde user name that matches the headers' From:
240:      *                 address or null if the users can't be listed or no
241:      *                 match has been found.
242:      */
243:     static protected function _findAuthUser($from)
244:     {
245:         $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
246: 
247:         if ($auth->hasCapability('list')) {
248:             foreach ($auth->listUsers() as $user) {
249:                 $identity = $GLOBALS['injector']
250:                     ->getInstance('Horde_Core_Factory_Identity')
251:                     ->create($user);
252:                 $addrs = $identity->getAll('from_addr');
253:                 foreach ($addrs as $addr) {
254:                     if (strcasecmp($from, $addr) == 0) {
255:                         return $user;
256:                     }
257:                 }
258:             }
259:         }
260: 
261:         return false;
262:     }
263: 
264: }
265: 
API documentation generated by ApiGen