Merge pull request #17122 from colemanw/buildOptions
[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
43155999 79 return self::formatMailUnrecognisedPart($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
43155999 113 return self::formatMailUnrecognisedPart($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
325 *
326 * @return array
327 */
a5989abf 328 public static function parseMailingObject(&$mail) {
6a488035
TO
329
330 $config = CRM_Core_Config::singleton();
331
332 // get ready for collecting data about this email
333 // and put it in a standardized format
be2fb01f 334 $params = ['is_error' => 0];
6a488035 335
e23ad041
CB
336 // Sometimes $mail->from is unset because ezcMail didn't handle format
337 // of From header. CRM-19215.
338 if (!isset($mail->from)) {
339 if (preg_match('/^([^ ]*)( (.*))?$/', $mail->getHeader('from'), $matches)) {
340 $mail->from = new ezcMailAddress($matches[1], trim($matches[2]));
341 }
342 }
343
be2fb01f 344 $params['from'] = [];
6a488035
TO
345 self::parseAddress($mail->from, $field, $params['from'], $mail);
346
347 // we definitely need a contact id for the from address
348 // if we dont have one, skip this email
349 if (empty($params['from']['id'])) {
408b79bf 350 return NULL;
6a488035
TO
351 }
352
be2fb01f 353 $emailFields = ['to', 'cc', 'bcc'];
6a488035
TO
354 foreach ($emailFields as $field) {
355 $value = $mail->$field;
356 self::parseAddresses($value, $field, $params, $mail);
357 }
358
359 // define other parameters
360 $params['subject'] = $mail->subject;
361 $params['date'] = date("YmdHi00",
362 strtotime($mail->getHeader("Date"))
363 );
be2fb01f 364 $attachments = [];
6a488035
TO
365 $params['body'] = self::formatMailPart($mail->body, $attachments);
366
367 // format and move attachments to the civicrm area
368 if (!empty($attachments)) {
85ebb706 369 $date = date('YmdHis');
6a488035
TO
370 $config = CRM_Core_Config::singleton();
371 for ($i = 0; $i < count($attachments); $i++) {
372 $attachNum = $i + 1;
353ffa53
TO
373 $fileName = basename($attachments[$i]['fullName']);
374 $newName = CRM_Utils_File::makeFileName($fileName);
375 $location = $config->uploadDir . $newName;
6a488035
TO
376
377 // move file to the civicrm upload directory
378 rename($attachments[$i]['fullName'], $location);
379
380 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
381
be2fb01f 382 $params["attachFile_$attachNum"] = [
6a488035
TO
383 'uri' => $fileName,
384 'type' => $mimeType,
385 'upload_date' => $date,
386 'location' => $location,
be2fb01f 387 ];
6a488035
TO
388 }
389 }
390
391 return $params;
392 }
393
5bc392e6
EM
394 /**
395 * @param $address
c490a46a 396 * @param array $params
5bc392e6
EM
397 * @param $subParam
398 * @param $mail
399 */
a5989abf 400 public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
6a488035
TO
401 // CRM-9484
402 if (empty($address->email)) {
403 return;
404 }
405
406 $subParam['email'] = $address->email;
407 $subParam['name'] = $address->name;
408
6a488035
TO
409 $contactID = self::getContactID($subParam['email'],
410 $subParam['name'],
411 TRUE,
412 $mail
413 );
414 $subParam['id'] = $contactID ? $contactID : NULL;
415 }
416
5bc392e6
EM
417 /**
418 * @param $addresses
419 * @param $token
c490a46a 420 * @param array $params
5bc392e6
EM
421 * @param $mail
422 */
a5989abf 423 public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
be2fb01f 424 $params[$token] = [];
6a488035
TO
425
426 foreach ($addresses as $address) {
be2fb01f 427 $subParam = [];
6a488035
TO
428 self::parseAddress($address, $params, $subParam, $mail);
429 $params[$token][] = $subParam;
430 }
431 }
432
433 /**
fe482240 434 * Retrieve a contact ID and if not present.
ea3ddccf 435 *
436 * Create one with this email
437 *
438 * @param string $email
439 * @param string $name
440 * @param bool $create
441 * @param string $mail
442 *
443 * @return int|null
6a488035 444 */
a5989abf 445 public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
6a488035
TO
446 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($email, 'Individual');
447
448 $contactID = NULL;
449 if ($dao) {
450 $contactID = $dao->contact_id;
451 }
452
453 $result = NULL;
454 CRM_Utils_Hook::emailProcessorContact($email, $contactID, $result);
455
456 if (!empty($result)) {
457 if ($result['action'] == self::EMAILPROCESSOR_IGNORE) {
458 return NULL;
459 }
460 if ($result['action'] == self::EMAILPROCESSOR_OVERRIDE) {
461 return $result['contactID'];
462 }
463
464 // else this is now create individual
465 // so we just fall out and do what we normally do
466 }
467
468 if ($contactID) {
469 return $contactID;
470 }
471
472 if (!$create) {
473 return NULL;
474 }
475
476 // contact does not exist, lets create it
be2fb01f 477 $params = [
6a488035
TO
478 'contact_type' => 'Individual',
479 'email-Primary' => $email,
be2fb01f 480 ];
6a488035
TO
481
482 CRM_Utils_String::extractName($name, $params);
483
db62d3a5 484 return CRM_Contact_BAO_Contact::createProfileContact($params);
6a488035 485 }
96025800 486
6a488035 487}