3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
33 class CRM_Utils_Mail_Incoming
{
35 EMAILPROCESSOR_CREATE_INDIVIDUAL
= 1,
36 EMAILPROCESSOR_OVERRIDE
= 2,
37 EMAILPROCESSOR_IGNORE
= 3;
45 public static function formatMail($mail, &$attachments) {
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";
55 $t .= self
::formatMailPart($mail->body
, $attachments);
65 public static function formatMailPart($part, &$attachments) {
66 if ($part instanceof ezcMail
) {
67 return self
::formatMail($part, $attachments);
70 if ($part instanceof ezcMailText
) {
71 return self
::formatMailText($part, $attachments);
74 if ($part instanceof ezcMailFile
) {
75 return self
::formatMailFile($part, $attachments);
78 if ($part instanceof ezcMailRfc822Digest
) {
79 return self
::formatMailRfc822Digest($part, $attachments);
82 if ($part instanceof ezcMailMultiPart
) {
83 return self
::formatMailMultipart($part, $attachments);
86 if ($part instanceof ezcMailDeliveryStatus
) {
87 return self
::formatMailDeliveryStatus($part);
90 // CRM-19111 - Handle blank emails with a subject.
95 return self
::formatMailUnrecognisedPart($part);
100 * @param $attachments
104 public static function formatMailMultipart($part, &$attachments) {
105 if ($part instanceof ezcMailMultiPartAlternative
) {
106 return self
::formatMailMultipartAlternative($part, $attachments);
109 if ($part instanceof ezcMailMultiPartDigest
) {
110 return self
::formatMailMultipartDigest($part, $attachments);
113 if ($part instanceof ezcMailMultiPartRelated
) {
114 return self
::formatMailMultipartRelated($part, $attachments);
117 if ($part instanceof ezcMailMultiPartMixed
) {
118 return self
::formatMailMultipartMixed($part, $attachments);
121 if ($part instanceof ezcMailMultipartReport
) {
122 return self
::formatMailMultipartReport($part, $attachments);
125 if ($part instanceof ezcMailDeliveryStatus
) {
126 return self
::formatMailDeliveryStatus($part);
129 return self
::formatMailUnrecognisedPart($part);
134 * @param $attachments
138 public static function formatMailMultipartMixed($part, &$attachments) {
140 foreach ($part->getParts() as $key => $alternativePart) {
141 $t .= self
::formatMailPart($alternativePart, $attachments);
148 * @param $attachments
152 public static function formatMailMultipartRelated($part, &$attachments) {
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);
160 $t .= "-RELATED END-\n";
166 * @param $attachments
170 public static function formatMailMultipartDigest($part, &$attachments) {
172 foreach ($part->getParts() as $key => $alternativePart) {
173 $t .= "-DIGEST-$key-\n";
174 $t .= self
::formatMailPart($alternativePart, $attachments);
176 $t .= "-DIGEST END---\n";
182 * @param $attachments
186 public static function formatMailRfc822Digest($part, &$attachments) {
188 $t .= "-DIGEST-ITEM-\n";
190 $t .= self
::formatMailpart($part->mail
, $attachments);
191 $t .= "-DIGEST ITEM END-\n";
197 * @param $attachments
201 public static function formatMailMultipartAlternative($part, &$attachments) {
203 foreach ($part->getParts() as $key => $alternativePart) {
204 $t .= "-ALTERNATIVE ITEM $key-\n";
205 $t .= self
::formatMailPart($alternativePart, $attachments);
207 $t .= "-ALTERNATIVE END-\n";
213 * @param $attachments
217 public static function formatMailText($part, &$attachments) {
218 $t = "\n{$part->text}\n";
224 * @param $attachments
228 public static function formatMailMultipartReport($part, &$attachments) {
230 foreach ($part->getParts() as $key => $reportPart) {
231 $t .= "-REPORT-$key-\n";
232 $t .= self
::formatMailPart($reportPart, $attachments);
234 $t .= "-REPORT END---\n";
243 public static function formatMailDeliveryStatus($part) {
245 $t .= "-DELIVERY STATUS BEGIN-\n";
246 $t .= $part->generateBody();
247 $t .= "-DELIVERY STATUS END-\n";
256 public function formatUnrecognisedPart($part) {
257 CRM_Core_Error
::debug_log_message(ts('CRM_Utils_Mail_Incoming: Unable to handle message part of type "%1".', ['%1' => get_class($part)]));
258 return ts('Unrecognised message part of type "%1".', ['%1' => get_class($part)]);
263 * @param $attachments
267 public static function formatMailFile($part, &$attachments) {
269 'dispositionType' => $part->dispositionType
,
270 'contentType' => $part->contentType
,
271 'mimeType' => $part->mimeType
,
272 'contentID' => $part->contentId
,
273 'fullName' => $part->fileName
,
283 public static function formatAddresses($addresses) {
285 foreach ($addresses as $address) {
286 $fa[] = self
::formatAddress($address);
288 return implode(', ', $fa);
296 public static function formatAddress($address) {
298 if (!empty($address->name
)) {
299 $name = "{$address->name} ";
301 return $name . "<{$address->email}>";
310 public function &parse(&$file) {
312 // check that the file exists and has some content
313 if (!file_exists($file) ||
314 !trim(file_get_contents($file))
316 return CRM_Core_Error
::createAPIError(ts('%1 does not exists or is empty',
321 // explode email to digestable format
322 $set = new ezcMailFileSet([$file]);
323 $parser = new ezcMailParser();
324 $mail = $parser->parseMail($set);
327 return CRM_Core_Error
::createAPIError(ts('%1 could not be parsed',
332 // since we only have one fileset
335 $mailParams = self
::parseMailingObject($mail);
344 public static function parseMailingObject(&$mail) {
346 $config = CRM_Core_Config
::singleton();
348 // get ready for collecting data about this email
349 // and put it in a standardized format
350 $params = ['is_error' => 0];
352 // Sometimes $mail->from is unset because ezcMail didn't handle format
353 // of From header. CRM-19215.
354 if (!isset($mail->from
)) {
355 if (preg_match('/^([^ ]*)( (.*))?$/', $mail->getHeader('from'), $matches)) {
356 $mail->from
= new ezcMailAddress($matches[1], trim($matches[2]));
360 $params['from'] = [];
361 self
::parseAddress($mail->from
, $field, $params['from'], $mail);
363 // we definitely need a contact id for the from address
364 // if we dont have one, skip this email
365 if (empty($params['from']['id'])) {
369 $emailFields = ['to', 'cc', 'bcc'];
370 foreach ($emailFields as $field) {
371 $value = $mail->$field;
372 self
::parseAddresses($value, $field, $params, $mail);
375 // define other parameters
376 $params['subject'] = $mail->subject
;
377 $params['date'] = date("YmdHi00",
378 strtotime($mail->getHeader("Date"))
381 $params['body'] = self
::formatMailPart($mail->body
, $attachments);
383 // format and move attachments to the civicrm area
384 if (!empty($attachments)) {
385 $date = date('YmdHis');
386 $config = CRM_Core_Config
::singleton();
387 for ($i = 0; $i < count($attachments); $i++
) {
389 $fileName = basename($attachments[$i]['fullName']);
390 $newName = CRM_Utils_File
::makeFileName($fileName);
391 $location = $config->uploadDir
. $newName;
393 // move file to the civicrm upload directory
394 rename($attachments[$i]['fullName'], $location);
396 $mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
398 $params["attachFile_$attachNum"] = [
401 'upload_date' => $date,
402 'location' => $location,
412 * @param array $params
416 public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
418 if (empty($address->email
)) {
422 $subParam['email'] = $address->email
;
423 $subParam['name'] = $address->name
;
425 $contactID = self
::getContactID($subParam['email'],
430 $subParam['id'] = $contactID ?
$contactID : NULL;
436 * @param array $params
439 public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
440 $params[$token] = [];
442 foreach ($addresses as $address) {
444 self
::parseAddress($address, $params, $subParam, $mail);
445 $params[$token][] = $subParam;
450 * Retrieve a contact ID and if not present.
452 * Create one with this email
454 * @param string $email
455 * @param string $name
456 * @param bool $create
457 * @param string $mail
461 public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
462 $dao = CRM_Contact_BAO_Contact
::matchContactOnEmail($email, 'Individual');
466 $contactID = $dao->contact_id
;
470 CRM_Utils_Hook
::emailProcessorContact($email, $contactID, $result);
472 if (!empty($result)) {
473 if ($result['action'] == self
::EMAILPROCESSOR_IGNORE
) {
476 if ($result['action'] == self
::EMAILPROCESSOR_OVERRIDE
) {
477 return $result['contactID'];
480 // else this is now create individual
481 // so we just fall out and do what we normally do
492 // contact does not exist, lets create it
494 'contact_type' => 'Individual',
495 'email-Primary' => $email,
498 CRM_Utils_String
::extractName($name, $params);
500 return CRM_Contact_BAO_Contact
::createProfileContact($params,
501 CRM_Core_DAO
::$_nullArray