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);
328 public static function parseMailingObject(&$mail) {
330 $config = CRM_Core_Config
::singleton();
332 // get ready for collecting data about this email
333 // and put it in a standardized format
334 $params = ['is_error' => 0];
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]));
344 $params['from'] = [];
345 self
::parseAddress($mail->from
, $field, $params['from'], $mail);
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'])) {
353 $emailFields = ['to', 'cc', 'bcc'];
354 foreach ($emailFields as $field) {
355 $value = $mail->$field;
356 self
::parseAddresses($value, $field, $params, $mail);
359 // define other parameters
360 $params['subject'] = $mail->subject
;
361 $params['date'] = date("YmdHi00",
362 strtotime($mail->getHeader("Date"))
365 $params['body'] = self
::formatMailPart($mail->body
, $attachments);
367 // format and move attachments to the civicrm area
368 if (!empty($attachments)) {
369 $date = date('YmdHis');
370 $config = CRM_Core_Config
::singleton();
371 for ($i = 0; $i < count($attachments); $i++
) {
373 $fileName = basename($attachments[$i]['fullName']);
374 $newName = CRM_Utils_File
::makeFileName($fileName);
375 $location = $config->uploadDir
. $newName;
377 // move file to the civicrm upload directory
378 rename($attachments[$i]['fullName'], $location);
380 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
382 $params["attachFile_$attachNum"] = [
385 'upload_date' => $date,
386 'location' => $location,
396 * @param array $params
400 public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
402 if (empty($address->email
)) {
406 $subParam['email'] = $address->email
;
407 $subParam['name'] = $address->name
;
409 $contactID = self
::getContactID($subParam['email'],
414 $subParam['id'] = $contactID ?
$contactID : NULL;
420 * @param array $params
423 public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
424 $params[$token] = [];
426 foreach ($addresses as $address) {
428 self
::parseAddress($address, $params, $subParam, $mail);
429 $params[$token][] = $subParam;
434 * Retrieve a contact ID and if not present.
436 * Create one with this email
438 * @param string $email
439 * @param string $name
440 * @param bool $create
441 * @param string $mail
445 public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
446 $dao = CRM_Contact_BAO_Contact
::matchContactOnEmail($email, 'Individual');
450 $contactID = $dao->contact_id
;
454 CRM_Utils_Hook
::emailProcessorContact($email, $contactID, $result);
456 if (!empty($result)) {
457 if ($result['action'] == self
::EMAILPROCESSOR_IGNORE
) {
460 if ($result['action'] == self
::EMAILPROCESSOR_OVERRIDE
) {
461 return $result['contactID'];
464 // else this is now create individual
465 // so we just fall out and do what we normally do
476 // contact does not exist, lets create it
478 'contact_type' => 'Individual',
479 'email-Primary' => $email,
482 CRM_Utils_String
::extractName($name, $params);
484 return CRM_Contact_BAO_Contact
::createProfileContact($params);