3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Utils_Mail_Incoming
{
19 EMAILPROCESSOR_CREATE_INDIVIDUAL
= 1,
20 EMAILPROCESSOR_OVERRIDE
= 2,
21 EMAILPROCESSOR_IGNORE
= 3;
29 public static function formatMail($mail, &$attachments) {
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";
39 $t .= self
::formatMailPart($mail->body
, $attachments);
49 public static function formatMailPart($part, &$attachments) {
50 if ($part instanceof ezcMail
) {
51 return self
::formatMail($part, $attachments);
54 if ($part instanceof ezcMailText
) {
55 return self
::formatMailText($part, $attachments);
58 if ($part instanceof ezcMailFile
) {
59 return self
::formatMailFile($part, $attachments);
62 if ($part instanceof ezcMailRfc822Digest
) {
63 return self
::formatMailRfc822Digest($part, $attachments);
66 if ($part instanceof ezcMailMultiPart
) {
67 return self
::formatMailMultipart($part, $attachments);
70 if ($part instanceof ezcMailDeliveryStatus
) {
71 return self
::formatMailDeliveryStatus($part);
74 // CRM-19111 - Handle blank emails with a subject.
79 return self
::formatMailUnrecognisedPart($part);
88 public static function formatMailMultipart($part, &$attachments) {
89 if ($part instanceof ezcMailMultiPartAlternative
) {
90 return self
::formatMailMultipartAlternative($part, $attachments);
93 if ($part instanceof ezcMailMultiPartDigest
) {
94 return self
::formatMailMultipartDigest($part, $attachments);
97 if ($part instanceof ezcMailMultiPartRelated
) {
98 return self
::formatMailMultipartRelated($part, $attachments);
101 if ($part instanceof ezcMailMultiPartMixed
) {
102 return self
::formatMailMultipartMixed($part, $attachments);
105 if ($part instanceof ezcMailMultipartReport
) {
106 return self
::formatMailMultipartReport($part, $attachments);
109 if ($part instanceof ezcMailDeliveryStatus
) {
110 return self
::formatMailDeliveryStatus($part);
113 return self
::formatMailUnrecognisedPart($part);
118 * @param $attachments
122 public static function formatMailMultipartMixed($part, &$attachments) {
124 foreach ($part->getParts() as $key => $alternativePart) {
125 $t .= self
::formatMailPart($alternativePart, $attachments);
132 * @param $attachments
136 public static function formatMailMultipartRelated($part, &$attachments) {
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);
144 $t .= "-RELATED END-\n";
150 * @param $attachments
154 public static function formatMailMultipartDigest($part, &$attachments) {
156 foreach ($part->getParts() as $key => $alternativePart) {
157 $t .= "-DIGEST-$key-\n";
158 $t .= self
::formatMailPart($alternativePart, $attachments);
160 $t .= "-DIGEST END---\n";
166 * @param $attachments
170 public static function formatMailRfc822Digest($part, &$attachments) {
172 $t .= "-DIGEST-ITEM-\n";
174 $t .= self
::formatMailpart($part->mail
, $attachments);
175 $t .= "-DIGEST ITEM END-\n";
181 * @param $attachments
185 public static function formatMailMultipartAlternative($part, &$attachments) {
187 foreach ($part->getParts() as $key => $alternativePart) {
188 $t .= "-ALTERNATIVE ITEM $key-\n";
189 $t .= self
::formatMailPart($alternativePart, $attachments);
191 $t .= "-ALTERNATIVE END-\n";
197 * @param $attachments
201 public static function formatMailText($part, &$attachments) {
202 $t = "\n{$part->text}\n";
208 * @param $attachments
212 public static function formatMailMultipartReport($part, &$attachments) {
214 foreach ($part->getParts() as $key => $reportPart) {
215 $t .= "-REPORT-$key-\n";
216 $t .= self
::formatMailPart($reportPart, $attachments);
218 $t .= "-REPORT END---\n";
227 public static function formatMailDeliveryStatus($part) {
229 $t .= "-DELIVERY STATUS BEGIN-\n";
230 $t .= $part->generateBody();
231 $t .= "-DELIVERY STATUS END-\n";
240 public function formatUnrecognisedPart($part) {
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)]);
247 * @param $attachments
251 public static function formatMailFile($part, &$attachments) {
253 'dispositionType' => $part->dispositionType
,
254 'contentType' => $part->contentType
,
255 'mimeType' => $part->mimeType
,
256 'contentID' => $part->contentId
,
257 'fullName' => $part->fileName
,
267 public static function formatAddresses($addresses) {
269 foreach ($addresses as $address) {
270 $fa[] = self
::formatAddress($address);
272 return implode(', ', $fa);
280 public static function formatAddress($address) {
282 if (!empty($address->name
)) {
283 $name = "{$address->name} ";
285 return $name . "<{$address->email}>";
294 public function &parse(&$file) {
296 // check that the file exists and has some content
297 if (!file_exists($file) ||
298 !trim(file_get_contents($file))
300 return CRM_Core_Error
::createAPIError(ts('%1 does not exists or is empty',
305 // explode email to digestable format
306 $set = new ezcMailFileSet([$file]);
307 $parser = new ezcMailParser();
308 $mail = $parser->parseMail($set);
311 return CRM_Core_Error
::createAPIError(ts('%1 could not be parsed',
316 // since we only have one fileset
319 $mailParams = self
::parseMailingObject($mail);
325 * @param $createContact
326 * @param $requireContact
330 public static function parseMailingObject(&$mail, $createContact = TRUE, $requireContact = TRUE) {
332 $config = CRM_Core_Config
::singleton();
334 // get ready for collecting data about this email
335 // and put it in a standardized format
336 $params = ['is_error' => 0];
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]));
346 $params['from'] = [];
347 self
::parseAddress($mail->from
, $field, $params['from'], $mail, $createContact);
349 // we definitely need a contact id for the from address
350 // if we dont have one, skip this email
351 if ($requireContact && empty($params['from']['id'])) {
355 $emailFields = ['to', 'cc', 'bcc'];
356 foreach ($emailFields as $field) {
357 $value = $mail->$field;
358 self
::parseAddresses($value, $field, $params, $mail, $createContact);
361 // define other parameters
362 $params['subject'] = $mail->subject
;
363 $params['date'] = date("YmdHi00",
364 strtotime($mail->getHeader("Date"))
367 $params['body'] = self
::formatMailPart($mail->body
, $attachments);
369 // format and move attachments to the civicrm area
370 if (!empty($attachments)) {
371 $date = date('YmdHis');
372 $config = CRM_Core_Config
::singleton();
373 for ($i = 0; $i < count($attachments); $i++
) {
375 $fileName = basename($attachments[$i]['fullName']);
376 $newName = CRM_Utils_File
::makeFileName($fileName);
377 $location = $config->uploadDir
. $newName;
379 // move file to the civicrm upload directory
380 rename($attachments[$i]['fullName'], $location);
382 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
384 $params["attachFile_$attachNum"] = [
387 'upload_date' => $date,
388 'location' => $location,
398 * @param array $params
401 * @param $createContact
403 public static function parseAddress(&$address, &$params, &$subParam, &$mail, $createContact = TRUE) {
405 if (empty($address->email
)) {
409 $subParam['email'] = $address->email
;
410 $subParam['name'] = $address->name
;
412 $contactID = self
::getContactID($subParam['email'],
417 $subParam['id'] = $contactID ?
$contactID : NULL;
423 * @param array $params
425 * @param $createContact
427 public static function parseAddresses(&$addresses, $token, &$params, &$mail, $createContact = TRUE) {
428 $params[$token] = [];
430 foreach ($addresses as $address) {
432 self
::parseAddress($address, $params, $subParam, $mail, $createContact);
433 $params[$token][] = $subParam;
438 * Retrieve a contact ID and if not present.
440 * Create one with this email
442 * @param string $email
443 * @param string $name
444 * @param bool $create
445 * @param string $mail
449 public static function getContactID($email, $name, $create, &$mail) {
450 $dao = CRM_Contact_BAO_Contact
::matchContactOnEmail($email, 'Individual');
454 $contactID = $dao->contact_id
;
456 $dao = CRM_Contact_BAO_Contact
::matchContactOnEmail($email, 'Organization');
459 $contactID = $dao->contact_id
;
464 CRM_Utils_Hook
::emailProcessorContact($email, $contactID, $result);
466 if (!empty($result)) {
467 if ($result['action'] == self
::EMAILPROCESSOR_IGNORE
) {
470 if ($result['action'] == self
::EMAILPROCESSOR_OVERRIDE
) {
471 return $result['contactID'];
474 // else this is now create individual
475 // so we just fall out and do what we normally do
486 // contact does not exist, lets create it
488 'contact_type' => 'Individual',
489 'email-Primary' => $email,
492 CRM_Utils_String
::extractName($name, $params);
494 return CRM_Contact_BAO_Contact
::createProfileContact($params);