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