CRM-9484: Log unrecognised message part types to debug log.
[civicrm-core.git] / CRM / Utils / Mail / Incoming.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
fa938177 6 | Copyright CiviCRM LLC (c) 2004-2016 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
fa938177 31 * @copyright CiviCRM LLC (c) 2004-2016
6a488035
TO
32 */
33class CRM_Utils_Mail_Incoming {
7da04cde 34 const
6a488035
TO
35 EMAILPROCESSOR_CREATE_INDIVIDUAL = 1,
36 EMAILPROCESSOR_OVERRIDE = 2,
37 EMAILPROCESSOR_IGNORE = 3;
38
5bc392e6
EM
39 /**
40 * @param $mail
41 * @param $attachments
42 *
43 * @return string
44 */
00be9182 45 public function formatMail($mail, &$attachments) {
6a488035
TO
46 $t = '';
47 $t .= "From: " . self::formatAddress($mail->from) . "\n";
48 $t .= "To: " . self::formatAddresses($mail->to) . "\n";
49 $t .= "Cc: " . self::formatAddresses($mail->cc) . "\n";
50 $t .= "Bcc: " . self::formatAddresses($mail->bcc) . "\n";
51 $t .= 'Date: ' . date(DATE_RFC822, $mail->timestamp) . "\n";
52 $t .= 'Subject: ' . $mail->subject . "\n";
53 $t .= "MessageId: " . $mail->messageId . "\n";
54 $t .= "\n";
55 $t .= self::formatMailPart($mail->body, $attachments);
56 return $t;
57 }
58
5bc392e6
EM
59 /**
60 * @param $part
61 * @param $attachments
62 *
63 * @throws Exception
64 */
a5989abf 65 public static function formatMailPart($part, &$attachments) {
6a488035
TO
66 if ($part instanceof ezcMail) {
67 return self::formatMail($part, $attachments);
68 }
69
7bc2d958 70 if ($part instanceof ezcMailText) {
6a488035
TO
71 return self::formatMailText($part, $attachments);
72 }
73
74 if ($part instanceof ezcMailFile) {
75 return self::formatMailFile($part, $attachments);
76 }
77
78 if ($part instanceof ezcMailRfc822Digest) {
79 return self::formatMailRfc822Digest($part, $attachments);
80 }
81
82 if ($part instanceof ezcMailMultiPart) {
83 return self::formatMailMultipart($part, $attachments);
84 }
85
6a4eebc4
CB
86 if ($part instanceof ezcMailDeliveryStatus) {
87 return self::formatMailDeliveryStatus($part);
88 }
89
7bc2d958
J
90 // CRM-19111 - Handle blank emails with a subject.
91 if (!$part) {
92 return NULL;
93 }
94
43155999 95 return self::formatMailUnrecognisedPart($part);
6a488035
TO
96 }
97
5bc392e6
EM
98 /**
99 * @param $part
100 * @param $attachments
101 *
102 * @throws Exception
103 */
00be9182 104 public function formatMailMultipart($part, &$attachments) {
6a488035
TO
105 if ($part instanceof ezcMailMultiPartAlternative) {
106 return self::formatMailMultipartAlternative($part, $attachments);
107 }
108
109 if ($part instanceof ezcMailMultiPartDigest) {
110 return self::formatMailMultipartDigest($part, $attachments);
111 }
112
113 if ($part instanceof ezcMailMultiPartRelated) {
114 return self::formatMailMultipartRelated($part, $attachments);
115 }
116
117 if ($part instanceof ezcMailMultiPartMixed) {
118 return self::formatMailMultipartMixed($part, $attachments);
119 }
120
121 if ($part instanceof ezcMailMultipartReport) {
122 return self::formatMailMultipartReport($part, $attachments);
123 }
124
6a4eebc4
CB
125 if ($part instanceof ezcMailDeliveryStatus) {
126 return self::formatMailDeliveryStatus($part);
127 }
128
43155999 129 return self::formatMailUnrecognisedPart($part);
6a488035
TO
130 }
131
5bc392e6
EM
132 /**
133 * @param $part
134 * @param $attachments
135 *
136 * @return string
137 */
00be9182 138 public function formatMailMultipartMixed($part, &$attachments) {
6a488035
TO
139 $t = '';
140 foreach ($part->getParts() as $key => $alternativePart) {
141 $t .= self::formatMailPart($alternativePart, $attachments);
142 }
143 return $t;
144 }
145
5bc392e6
EM
146 /**
147 * @param $part
148 * @param $attachments
149 *
150 * @return string
151 */
00be9182 152 public function formatMailMultipartRelated($part, &$attachments) {
6a488035
TO
153 $t = '';
154 $t .= "-RELATED MAIN PART-\n";
155 $t .= self::formatMailPart($part->getMainPart(), $attachments);
156 foreach ($part->getRelatedParts() as $key => $alternativePart) {
157 $t .= "-RELATED PART $key-\n";
158 $t .= self::formatMailPart($alternativePart, $attachments);
159 }
160 $t .= "-RELATED END-\n";
161 return $t;
162 }
163
5bc392e6
EM
164 /**
165 * @param $part
166 * @param $attachments
167 *
168 * @return string
169 */
00be9182 170 public function formatMailMultipartDigest($part, &$attachments) {
6a488035
TO
171 $t = '';
172 foreach ($part->getParts() as $key => $alternativePart) {
173 $t .= "-DIGEST-$key-\n";
174 $t .= self::formatMailPart($alternativePart, $attachments);
175 }
176 $t .= "-DIGEST END---\n";
177 return $t;
178 }
179
5bc392e6
EM
180 /**
181 * @param $part
182 * @param $attachments
183 *
184 * @return string
185 */
00be9182 186 public function formatMailRfc822Digest($part, &$attachments) {
6a488035
TO
187 $t = '';
188 $t .= "-DIGEST-ITEM-\n";
189 $t .= "Item:\n\n";
190 $t .= self::formatMailpart($part->mail, $attachments);
191 $t .= "-DIGEST ITEM END-\n";
192 return $t;
193 }
194
5bc392e6
EM
195 /**
196 * @param $part
197 * @param $attachments
198 *
199 * @return string
200 */
00be9182 201 public function formatMailMultipartAlternative($part, &$attachments) {
6a488035
TO
202 $t = '';
203 foreach ($part->getParts() as $key => $alternativePart) {
204 $t .= "-ALTERNATIVE ITEM $key-\n";
205 $t .= self::formatMailPart($alternativePart, $attachments);
206 }
207 $t .= "-ALTERNATIVE END-\n";
208 return $t;
209 }
210
5bc392e6
EM
211 /**
212 * @param $part
213 * @param $attachments
214 *
215 * @return string
216 */
a5989abf 217 public static function formatMailText($part, &$attachments) {
6a488035
TO
218 $t = "\n{$part->text}\n";
219 return $t;
220 }
221
5bc392e6
EM
222 /**
223 * @param $part
224 * @param $attachments
225 *
226 * @return string
227 */
00be9182 228 public function formatMailMultipartReport($part, &$attachments) {
6a488035
TO
229 $t = '';
230 foreach ($part->getParts() as $key => $reportPart) {
231 $t .= "-REPORT-$key-\n";
232 $t .= self::formatMailPart($reportPart, $attachments);
233 }
234 $t .= "-REPORT END---\n";
235 return $t;
236 }
237
6a4eebc4
CB
238 /**
239 * @param $part
240 *
241 * @return string
242 */
243 public function formatMailDeliveryStatus($part) {
244 $t = '';
245 $t .= "-DELIVERY STATUS BEGIN-\n";
246 $t .= $part->generateBody();
247 $t .= "-DELIVERY STATUS END-\n";
248 return $t;
249 }
250
43155999
CB
251 /**
252 * @param $part
253 *
254 * @return string
255 */
256 public function formatUnrecognisedPart($part) {
be9525b2 257 CRM_Core_Error::debug_log_message(ts('CRM_Utils_Mail_Incoming: Unable to handle message part of type "%1".', array('%1' => get_class($part))));
43155999
CB
258 return ts('Unrecognised message part of type "%1".', array('%1' => get_class($part)));
259 }
260
5bc392e6
EM
261 /**
262 * @param $part
263 * @param $attachments
264 *
265 * @return null
266 */
00be9182 267 public function formatMailFile($part, &$attachments) {
6a488035
TO
268 $attachments[] = array(
269 'dispositionType' => $part->dispositionType,
270 'contentType' => $part->contentType,
271 'mimeType' => $part->mimeType,
272 'contentID' => $part->contentId,
273 'fullName' => $part->fileName,
274 );
275 return NULL;
276 }
277
5bc392e6
EM
278 /**
279 * @param $addresses
280 *
281 * @return string
282 */
00be9182 283 public function formatAddresses($addresses) {
6a488035
TO
284 $fa = array();
285 foreach ($addresses as $address) {
286 $fa[] = self::formatAddress($address);
287 }
288 return implode(', ', $fa);
289 }
290
5bc392e6
EM
291 /**
292 * @param $address
293 *
294 * @return string
295 */
00be9182 296 public function formatAddress($address) {
6a488035
TO
297 $name = '';
298 if (!empty($address->name)) {
299 $name = "{$address->name} ";
300 }
301 return $name . "<{$address->email}>";
302 }
303
5bc392e6
EM
304 /**
305 * @param $file
306 *
307 * @return array
308 * @throws Exception
309 */
00be9182 310 public function &parse(&$file) {
6a488035
TO
311
312 // check that the file exists and has some content
313 if (!file_exists($file) ||
314 !trim(file_get_contents($file))
315 ) {
316 return CRM_Core_Error::createAPIError(ts('%1 does not exists or is empty',
353ffa53
TO
317 array(1 => $file)
318 ));
6a488035
TO
319 }
320
6a488035 321 // explode email to digestable format
353ffa53 322 $set = new ezcMailFileSet(array($file));
6a488035 323 $parser = new ezcMailParser();
353ffa53 324 $mail = $parser->parseMail($set);
6a488035
TO
325
326 if (!$mail) {
327 return CRM_Core_Error::createAPIError(ts('%1 could not be parsed',
353ffa53
TO
328 array(1 => $file)
329 ));
6a488035
TO
330 }
331
332 // since we only have one fileset
333 $mail = $mail[0];
334
335 $mailParams = self::parseMailingObject($mail);
336 return $mailParams;
337 }
338
5bc392e6
EM
339 /**
340 * @param $mail
341 *
342 * @return array
343 */
a5989abf 344 public static function parseMailingObject(&$mail) {
6a488035
TO
345
346 $config = CRM_Core_Config::singleton();
347
348 // get ready for collecting data about this email
349 // and put it in a standardized format
350 $params = array('is_error' => 0);
351
352 $params['from'] = array();
353 self::parseAddress($mail->from, $field, $params['from'], $mail);
354
355 // we definitely need a contact id for the from address
356 // if we dont have one, skip this email
357 if (empty($params['from']['id'])) {
408b79bf 358 return NULL;
6a488035
TO
359 }
360
361 $emailFields = array('to', 'cc', 'bcc');
362 foreach ($emailFields as $field) {
363 $value = $mail->$field;
364 self::parseAddresses($value, $field, $params, $mail);
365 }
366
367 // define other parameters
368 $params['subject'] = $mail->subject;
369 $params['date'] = date("YmdHi00",
370 strtotime($mail->getHeader("Date"))
371 );
372 $attachments = array();
373 $params['body'] = self::formatMailPart($mail->body, $attachments);
374
375 // format and move attachments to the civicrm area
376 if (!empty($attachments)) {
85ebb706 377 $date = date('YmdHis');
6a488035
TO
378 $config = CRM_Core_Config::singleton();
379 for ($i = 0; $i < count($attachments); $i++) {
380 $attachNum = $i + 1;
353ffa53
TO
381 $fileName = basename($attachments[$i]['fullName']);
382 $newName = CRM_Utils_File::makeFileName($fileName);
383 $location = $config->uploadDir . $newName;
6a488035
TO
384
385 // move file to the civicrm upload directory
386 rename($attachments[$i]['fullName'], $location);
387
388 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
389
390 $params["attachFile_$attachNum"] = array(
391 'uri' => $fileName,
392 'type' => $mimeType,
393 'upload_date' => $date,
394 'location' => $location,
395 );
396 }
397 }
398
399 return $params;
400 }
401
5bc392e6
EM
402 /**
403 * @param $address
c490a46a 404 * @param array $params
5bc392e6
EM
405 * @param $subParam
406 * @param $mail
407 */
a5989abf 408 public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
6a488035
TO
409 // CRM-9484
410 if (empty($address->email)) {
411 return;
412 }
413
414 $subParam['email'] = $address->email;
415 $subParam['name'] = $address->name;
416
6a488035
TO
417 $contactID = self::getContactID($subParam['email'],
418 $subParam['name'],
419 TRUE,
420 $mail
421 );
422 $subParam['id'] = $contactID ? $contactID : NULL;
423 }
424
5bc392e6
EM
425 /**
426 * @param $addresses
427 * @param $token
c490a46a 428 * @param array $params
5bc392e6
EM
429 * @param $mail
430 */
a5989abf 431 public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
6a488035
TO
432 $params[$token] = array();
433
434 foreach ($addresses as $address) {
435 $subParam = array();
436 self::parseAddress($address, $params, $subParam, $mail);
437 $params[$token][] = $subParam;
438 }
439 }
440
441 /**
fe482240 442 * Retrieve a contact ID and if not present.
ea3ddccf 443 *
444 * Create one with this email
445 *
446 * @param string $email
447 * @param string $name
448 * @param bool $create
449 * @param string $mail
450 *
451 * @return int|null
6a488035 452 */
a5989abf 453 public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
6a488035
TO
454 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($email, 'Individual');
455
456 $contactID = NULL;
457 if ($dao) {
458 $contactID = $dao->contact_id;
459 }
460
461 $result = NULL;
462 CRM_Utils_Hook::emailProcessorContact($email, $contactID, $result);
463
464 if (!empty($result)) {
465 if ($result['action'] == self::EMAILPROCESSOR_IGNORE) {
466 return NULL;
467 }
468 if ($result['action'] == self::EMAILPROCESSOR_OVERRIDE) {
469 return $result['contactID'];
470 }
471
472 // else this is now create individual
473 // so we just fall out and do what we normally do
474 }
475
476 if ($contactID) {
477 return $contactID;
478 }
479
480 if (!$create) {
481 return NULL;
482 }
483
484 // contact does not exist, lets create it
485 $params = array(
486 'contact_type' => 'Individual',
487 'email-Primary' => $email,
488 );
489
490 CRM_Utils_String::extractName($name, $params);
491
492 return CRM_Contact_BAO_Contact::createProfileContact($params,
493 CRM_Core_DAO::$_nullArray
494 );
495 }
96025800 496
6a488035 497}