First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
478
sites/all/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php
Normal file
478
sites/all/modules/civicrm/CRM/Utils/Mail/EmailProcessor.php
Normal file
|
@ -0,0 +1,478 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| This file is a part of CiviCRM. |
|
||||
| |
|
||||
| CiviCRM is free software; you can copy, modify, and distribute it |
|
||||
| under the terms of the GNU Affero General Public License |
|
||||
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
||||
| |
|
||||
| CiviCRM is distributed in the hope that it will be useful, but |
|
||||
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
||||
| See the GNU Affero General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU Affero General Public |
|
||||
| License and the CiviCRM Licensing Exception along |
|
||||
| with this program; if not, contact CiviCRM LLC |
|
||||
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
||||
| GNU Affero General Public License or the licensing of CiviCRM, |
|
||||
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
||||
+--------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @package CRM
|
||||
* @copyright CiviCRM LLC (c) 2004-2017
|
||||
*/
|
||||
|
||||
// we should consider moving these to the settings table
|
||||
// before the 4.1 release
|
||||
define('EMAIL_ACTIVITY_TYPE_ID', NULL);
|
||||
define('MAIL_BATCH_SIZE', 50);
|
||||
|
||||
/**
|
||||
* Class CRM_Utils_Mail_EmailProcessor.
|
||||
*/
|
||||
class CRM_Utils_Mail_EmailProcessor {
|
||||
|
||||
/**
|
||||
* Process the default mailbox (ie. that is used by civiMail for the bounce)
|
||||
*
|
||||
* @param bool $is_create_activities
|
||||
* Should activities be created
|
||||
*/
|
||||
public static function processBounces($is_create_activities) {
|
||||
$dao = new CRM_Core_DAO_MailSettings();
|
||||
$dao->domain_id = CRM_Core_Config::domainID();
|
||||
$dao->is_default = TRUE;
|
||||
$dao->find();
|
||||
|
||||
while ($dao->fetch()) {
|
||||
self::_process(TRUE, $dao, $is_create_activities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete old files from a given directory (recursively).
|
||||
*
|
||||
* @param string $dir
|
||||
* Directory to cleanup.
|
||||
* @param int $age
|
||||
* Files older than this many seconds will be deleted (default: 60 days).
|
||||
*/
|
||||
public static function cleanupDir($dir, $age = 5184000) {
|
||||
// return early if we can’t read/write the dir
|
||||
if (!is_writable($dir) or !is_readable($dir) or !is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (scandir($dir) as $file) {
|
||||
|
||||
// don’t go up the directory stack and skip new files/dirs
|
||||
if ($file == '.' or $file == '..') {
|
||||
continue;
|
||||
}
|
||||
if (filemtime("$dir/$file") > time() - $age) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// it’s an old file/dir, so delete/recurse
|
||||
is_dir("$dir/$file") ? self::cleanupDir("$dir/$file", $age) : unlink("$dir/$file");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the mailboxes that aren't default (ie. that aren't used by civiMail for the bounce).
|
||||
*/
|
||||
public static function processActivities() {
|
||||
$dao = new CRM_Core_DAO_MailSettings();
|
||||
$dao->domain_id = CRM_Core_Config::domainID();
|
||||
$dao->is_default = FALSE;
|
||||
$dao->find();
|
||||
$found = FALSE;
|
||||
while ($dao->fetch()) {
|
||||
$found = TRUE;
|
||||
self::_process(FALSE, $dao, TRUE);
|
||||
}
|
||||
if (!$found) {
|
||||
CRM_Core_Error::fatal(ts('No mailboxes have been configured for Email to Activity Processing'));
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the mailbox for all the settings from civicrm_mail_settings.
|
||||
*
|
||||
* @param bool|string $civiMail if true, processing is done in CiviMail context, or Activities otherwise.
|
||||
*/
|
||||
public static function process($civiMail = TRUE) {
|
||||
$dao = new CRM_Core_DAO_MailSettings();
|
||||
$dao->domain_id = CRM_Core_Config::domainID();
|
||||
$dao->find();
|
||||
|
||||
while ($dao->fetch()) {
|
||||
self::_process($civiMail, $dao);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $civiMail
|
||||
* @param CRM_Core_DAO_MailSettings $dao
|
||||
* @param bool $is_create_activities
|
||||
* Create activities.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function _process($civiMail, $dao, $is_create_activities) {
|
||||
// 0 = activities; 1 = bounce;
|
||||
$usedfor = $dao->is_default;
|
||||
|
||||
$emailActivityTypeId
|
||||
= (defined('EMAIL_ACTIVITY_TYPE_ID') && EMAIL_ACTIVITY_TYPE_ID)
|
||||
? EMAIL_ACTIVITY_TYPE_ID
|
||||
: CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Inbound Email');
|
||||
|
||||
if (!$emailActivityTypeId) {
|
||||
CRM_Core_Error::fatal(ts('Could not find a valid Activity Type ID for Inbound Email'));
|
||||
}
|
||||
|
||||
$config = CRM_Core_Config::singleton();
|
||||
$verpSeperator = preg_quote($config->verpSeparator);
|
||||
$twoDigitStringMin = $verpSeperator . '(\d+)' . $verpSeperator . '(\d+)';
|
||||
$twoDigitString = $twoDigitStringMin . $verpSeperator;
|
||||
$threeDigitString = $twoDigitString . '(\d+)' . $verpSeperator;
|
||||
|
||||
// FIXME: legacy regexen to handle CiviCRM 2.1 address patterns, with domain id and possible VERP part
|
||||
$commonRegex = '/^' . preg_quote($dao->localpart) . '(b|bounce|c|confirm|o|optOut|r|reply|re|e|resubscribe|u|unsubscribe)' . $threeDigitString . '([0-9a-f]{16})(-.*)?@' . preg_quote($dao->domain) . '$/';
|
||||
$subscrRegex = '/^' . preg_quote($dao->localpart) . '(s|subscribe)' . $twoDigitStringMin . '@' . preg_quote($dao->domain) . '$/';
|
||||
|
||||
// a common-for-all-actions regex to handle CiviCRM 2.2 address patterns
|
||||
$regex = '/^' . preg_quote($dao->localpart) . '(b|c|e|o|r|u)' . $twoDigitString . '([0-9a-f]{16})@' . preg_quote($dao->domain) . '$/';
|
||||
|
||||
// a tighter regex for finding bounce info in soft bounces’ mail bodies
|
||||
$rpRegex = '/Return-Path:\s*' . preg_quote($dao->localpart) . '(b)' . $twoDigitString . '([0-9a-f]{16})@' . preg_quote($dao->domain) . '/';
|
||||
|
||||
// a regex for finding bound info X-Header
|
||||
$rpXheaderRegex = '/X-CiviMail-Bounce: ' . preg_quote($dao->localpart) . '(b)' . $twoDigitString . '([0-9a-f]{16})@' . preg_quote($dao->domain) . '/i';
|
||||
// CiviMail in regex and Civimail in header !!!
|
||||
|
||||
// retrieve the emails
|
||||
try {
|
||||
$store = CRM_Mailing_MailStore::getStore($dao->name);
|
||||
}
|
||||
catch (Exception$e) {
|
||||
$message = ts('Could not connect to MailStore for ') . $dao->username . '@' . $dao->server . '<p>';
|
||||
$message .= ts('Error message: ');
|
||||
$message .= '<pre>' . $e->getMessage() . '</pre><p>';
|
||||
CRM_Core_Error::fatal($message);
|
||||
}
|
||||
|
||||
// process fifty at a time, CRM-4002
|
||||
while ($mails = $store->fetchNext(MAIL_BATCH_SIZE)) {
|
||||
foreach ($mails as $key => $mail) {
|
||||
|
||||
// for every addressee: match address elements if it's to CiviMail
|
||||
$matches = array();
|
||||
$action = NULL;
|
||||
|
||||
if ($usedfor == 1) {
|
||||
foreach ($mail->to as $address) {
|
||||
if (preg_match($regex, $address->email, $matches)) {
|
||||
list($match, $action, $job, $queue, $hash) = $matches;
|
||||
break;
|
||||
// FIXME: the below elseifs should be dropped when we drop legacy support
|
||||
}
|
||||
elseif (preg_match($commonRegex, $address->email, $matches)) {
|
||||
list($match, $action, $_, $job, $queue, $hash) = $matches;
|
||||
break;
|
||||
}
|
||||
elseif (preg_match($subscrRegex, $address->email, $matches)) {
|
||||
list($match, $action, $_, $job) = $matches;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// CRM-5471: if $matches is empty, it still might be a soft bounce sent
|
||||
// to another address, so scan the body for ‘Return-Path: …bounce-pattern…’
|
||||
if (!$matches and preg_match($rpRegex, $mail->generateBody(), $matches)) {
|
||||
list($match, $action, $job, $queue, $hash) = $matches;
|
||||
}
|
||||
|
||||
// if $matches is still empty, look for the X-CiviMail-Bounce header
|
||||
// CRM-9855
|
||||
if (!$matches and preg_match($rpXheaderRegex, $mail->generateBody(), $matches)) {
|
||||
list($match, $action, $job, $queue, $hash) = $matches;
|
||||
}
|
||||
// With Mandrilla, the X-CiviMail-Bounce header is produced by generateBody
|
||||
// is base64 encoded
|
||||
// Check all parts
|
||||
if (!$matches) {
|
||||
$all_parts = $mail->fetchParts();
|
||||
foreach ($all_parts as $k_part => $v_part) {
|
||||
if ($v_part instanceof ezcMailFile) {
|
||||
$p_file = $v_part->__get('fileName');
|
||||
$c_file = file_get_contents($p_file);
|
||||
if (preg_match($rpXheaderRegex, $c_file, $matches)) {
|
||||
list($match, $action, $job, $queue, $hash) = $matches;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if all else fails, check Delivered-To for possible pattern
|
||||
if (!$matches and preg_match($regex, $mail->getHeader('Delivered-To'), $matches)) {
|
||||
list($match, $action, $job, $queue, $hash) = $matches;
|
||||
}
|
||||
}
|
||||
|
||||
// preseve backward compatibility
|
||||
if ($usedfor == 0 || $is_create_activities) {
|
||||
// if its the activities that needs to be processed ..
|
||||
try {
|
||||
$mailParams = CRM_Utils_Mail_Incoming::parseMailingObject($mail);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
$store->markIgnored($key);
|
||||
continue;
|
||||
}
|
||||
|
||||
require_once 'CRM/Utils/DeprecatedUtils.php';
|
||||
$params = _civicrm_api3_deprecated_activity_buildmailparams($mailParams, $emailActivityTypeId);
|
||||
|
||||
$params['version'] = 3;
|
||||
if (!empty($dao->activity_status)) {
|
||||
$params['status_id'] = $dao->activity_status;
|
||||
}
|
||||
$result = civicrm_api('activity', 'create', $params);
|
||||
|
||||
if ($result['is_error']) {
|
||||
$matches = FALSE;
|
||||
echo "Failed Processing: {$mail->subject}. Reason: {$result['error_message']}\n";
|
||||
}
|
||||
else {
|
||||
$matches = TRUE;
|
||||
CRM_Utils_Hook::emailProcessor('activity', $params, $mail, $result);
|
||||
echo "Processed as Activity: {$mail->subject}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// if $matches is empty, this email is not CiviMail-bound
|
||||
if (!$matches) {
|
||||
$store->markIgnored($key);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get $replyTo from either the Reply-To header or from From
|
||||
// FIXME: make sure it works with Reply-Tos containing non-email stuff
|
||||
$replyTo = $mail->getHeader('Reply-To') ? $mail->getHeader('Reply-To') : $mail->from->email;
|
||||
|
||||
// handle the action by passing it to the proper API call
|
||||
// FIXME: leave only one-letter cases when dropping legacy support
|
||||
if (!empty($action)) {
|
||||
$result = NULL;
|
||||
|
||||
switch ($action) {
|
||||
case 'b':
|
||||
case 'bounce':
|
||||
$text = '';
|
||||
if ($mail->body instanceof ezcMailText) {
|
||||
$text = $mail->body->text;
|
||||
}
|
||||
elseif ($mail->body instanceof ezcMailMultipart) {
|
||||
if ($mail->body instanceof ezcMailMultipartReport) {
|
||||
$part = $mail->body->getMachinePart();
|
||||
if ($part instanceof ezcMailDeliveryStatus) {
|
||||
foreach ($part->recipients as $rec) {
|
||||
if (isset($rec["Diagnostic-Code"])) {
|
||||
$text = $rec["Diagnostic-Code"];
|
||||
break;
|
||||
}
|
||||
elseif (isset($rec["Description"])) {
|
||||
$text = $rec["Description"];
|
||||
break;
|
||||
}
|
||||
// no diagnostic info present - try getting the human readable part
|
||||
elseif (isset($rec["Status"])) {
|
||||
$text = $rec["Status"];
|
||||
$textpart = $mail->body->getReadablePart();
|
||||
if ($textpart != NULL and isset($textpart->text)) {
|
||||
$text .= " " . $textpart->text;
|
||||
}
|
||||
else {
|
||||
$text .= " Delivery failed but no diagnostic code or description.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($part != NULL and isset($part->text)) {
|
||||
$text = $part->text;
|
||||
}
|
||||
elseif (($part = $mail->body->getReadablePart()) != NULL) {
|
||||
$text = $part->text;
|
||||
}
|
||||
}
|
||||
elseif ($mail->body instanceof ezcMailMultipartRelated) {
|
||||
foreach ($mail->body->getRelatedParts() as $part) {
|
||||
if (isset($part->subType) and $part->subType == 'plain') {
|
||||
$text = $part->text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($mail->body->getParts() as $part) {
|
||||
if (isset($part->subType) and $part->subType == 'plain') {
|
||||
$text = $part->text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
empty($text) &&
|
||||
$mail->subject == "Delivery Status Notification (Failure)"
|
||||
) {
|
||||
// Exchange error - CRM-9361
|
||||
foreach ($mail->body->getParts() as $part) {
|
||||
if ($part instanceof ezcMailDeliveryStatus) {
|
||||
foreach ($part->recipients as $rec) {
|
||||
if ($rec["Status"] == "5.1.1") {
|
||||
if (isset($rec["Description"])) {
|
||||
$text = $rec["Description"];
|
||||
}
|
||||
else {
|
||||
$text = $rec["Status"] . " Delivery to the following recipients failed";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($text)) {
|
||||
// If bounce processing fails, just take the raw body. Cf. CRM-11046
|
||||
$text = $mail->generateBody();
|
||||
|
||||
// if text is still empty, lets fudge a blank text so the api call below will succeed
|
||||
if (empty($text)) {
|
||||
$text = ts('We could not extract the mail body from this bounce message.');
|
||||
}
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'job_id' => $job,
|
||||
'event_queue_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'body' => $text,
|
||||
'version' => 3,
|
||||
// Setting is_transactional means it will rollback if
|
||||
// it crashes part way through creating the bounce.
|
||||
// If the api were standard & had a create this would be the
|
||||
// default. Adding the standard api & deprecating this one
|
||||
// would probably be the
|
||||
// most consistent way to address this - but this is
|
||||
// a quick hack.
|
||||
'is_transactional' => 1,
|
||||
);
|
||||
$result = civicrm_api('Mailing', 'event_bounce', $params);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
case 'confirm':
|
||||
// CRM-7921
|
||||
$params = array(
|
||||
'contact_id' => $job,
|
||||
'subscribe_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('Mailing', 'event_confirm', $params);
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
case 'optOut':
|
||||
$params = array(
|
||||
'job_id' => $job,
|
||||
'event_queue_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('MailingGroup', 'event_domain_unsubscribe', $params);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
case 'reply':
|
||||
// instead of text and HTML parts (4th and 6th params) send the whole email as the last param
|
||||
$params = array(
|
||||
'job_id' => $job,
|
||||
'event_queue_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'bodyTxt' => NULL,
|
||||
'replyTo' => $replyTo,
|
||||
'bodyHTML' => NULL,
|
||||
'fullEmail' => $mail->generate(),
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('Mailing', 'event_reply', $params);
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
case 're':
|
||||
case 'resubscribe':
|
||||
$params = array(
|
||||
'job_id' => $job,
|
||||
'event_queue_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('MailingGroup', 'event_resubscribe', $params);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
case 'subscribe':
|
||||
$params = array(
|
||||
'email' => $mail->from->email,
|
||||
'group_id' => $job,
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('MailingGroup', 'event_subscribe', $params);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
case 'unsubscribe':
|
||||
$params = array(
|
||||
'job_id' => $job,
|
||||
'event_queue_id' => $queue,
|
||||
'hash' => $hash,
|
||||
'version' => 3,
|
||||
);
|
||||
$result = civicrm_api('MailingGroup', 'event_unsubscribe', $params);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($result['is_error']) {
|
||||
echo "Failed Processing: {$mail->subject}, Action: $action, Job ID: $job, Queue ID: $queue, Hash: $hash. Reason: {$result['error_message']}\n";
|
||||
}
|
||||
else {
|
||||
CRM_Utils_Hook::emailProcessor('mailing', $params, $mail, $result, $action);
|
||||
}
|
||||
}
|
||||
|
||||
$store->markProcessed($key);
|
||||
}
|
||||
// CRM-7356 – used by IMAP only
|
||||
$store->expunge();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
505
sites/all/modules/civicrm/CRM/Utils/Mail/Incoming.php
Normal file
505
sites/all/modules/civicrm/CRM/Utils/Mail/Incoming.php
Normal file
|
@ -0,0 +1,505 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| This file is a part of CiviCRM. |
|
||||
| |
|
||||
| CiviCRM is free software; you can copy, modify, and distribute it |
|
||||
| under the terms of the GNU Affero General Public License |
|
||||
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
|
||||
| |
|
||||
| CiviCRM is distributed in the hope that it will be useful, but |
|
||||
| WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
||||
| See the GNU Affero General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU Affero General Public |
|
||||
| License and the CiviCRM Licensing Exception along |
|
||||
| with this program; if not, contact CiviCRM LLC |
|
||||
| at info[AT]civicrm[DOT]org. If you have questions about the |
|
||||
| GNU Affero General Public License or the licensing of CiviCRM, |
|
||||
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
|
||||
+--------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @package CRM
|
||||
* @copyright CiviCRM LLC (c) 2004-2017
|
||||
*/
|
||||
class CRM_Utils_Mail_Incoming {
|
||||
const
|
||||
EMAILPROCESSOR_CREATE_INDIVIDUAL = 1,
|
||||
EMAILPROCESSOR_OVERRIDE = 2,
|
||||
EMAILPROCESSOR_IGNORE = 3;
|
||||
|
||||
/**
|
||||
* @param $mail
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMail($mail, &$attachments) {
|
||||
$t = '';
|
||||
$t .= "From: " . self::formatAddress($mail->from) . "\n";
|
||||
$t .= "To: " . self::formatAddresses($mail->to) . "\n";
|
||||
$t .= "Cc: " . self::formatAddresses($mail->cc) . "\n";
|
||||
$t .= "Bcc: " . self::formatAddresses($mail->bcc) . "\n";
|
||||
$t .= 'Date: ' . date(DATE_RFC822, $mail->timestamp) . "\n";
|
||||
$t .= 'Subject: ' . $mail->subject . "\n";
|
||||
$t .= "MessageId: " . $mail->messageId . "\n";
|
||||
$t .= "\n";
|
||||
$t .= self::formatMailPart($mail->body, $attachments);
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function formatMailPart($part, &$attachments) {
|
||||
if ($part instanceof ezcMail) {
|
||||
return self::formatMail($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailText) {
|
||||
return self::formatMailText($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailFile) {
|
||||
return self::formatMailFile($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailRfc822Digest) {
|
||||
return self::formatMailRfc822Digest($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailMultiPart) {
|
||||
return self::formatMailMultipart($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailDeliveryStatus) {
|
||||
return self::formatMailDeliveryStatus($part);
|
||||
}
|
||||
|
||||
// CRM-19111 - Handle blank emails with a subject.
|
||||
if (!$part) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self::formatMailUnrecognisedPart($part);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function formatMailMultipart($part, &$attachments) {
|
||||
if ($part instanceof ezcMailMultiPartAlternative) {
|
||||
return self::formatMailMultipartAlternative($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailMultiPartDigest) {
|
||||
return self::formatMailMultipartDigest($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailMultiPartRelated) {
|
||||
return self::formatMailMultipartRelated($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailMultiPartMixed) {
|
||||
return self::formatMailMultipartMixed($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailMultipartReport) {
|
||||
return self::formatMailMultipartReport($part, $attachments);
|
||||
}
|
||||
|
||||
if ($part instanceof ezcMailDeliveryStatus) {
|
||||
return self::formatMailDeliveryStatus($part);
|
||||
}
|
||||
|
||||
return self::formatMailUnrecognisedPart($part);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailMultipartMixed($part, &$attachments) {
|
||||
$t = '';
|
||||
foreach ($part->getParts() as $key => $alternativePart) {
|
||||
$t .= self::formatMailPart($alternativePart, $attachments);
|
||||
}
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailMultipartRelated($part, &$attachments) {
|
||||
$t = '';
|
||||
$t .= "-RELATED MAIN PART-\n";
|
||||
$t .= self::formatMailPart($part->getMainPart(), $attachments);
|
||||
foreach ($part->getRelatedParts() as $key => $alternativePart) {
|
||||
$t .= "-RELATED PART $key-\n";
|
||||
$t .= self::formatMailPart($alternativePart, $attachments);
|
||||
}
|
||||
$t .= "-RELATED END-\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailMultipartDigest($part, &$attachments) {
|
||||
$t = '';
|
||||
foreach ($part->getParts() as $key => $alternativePart) {
|
||||
$t .= "-DIGEST-$key-\n";
|
||||
$t .= self::formatMailPart($alternativePart, $attachments);
|
||||
}
|
||||
$t .= "-DIGEST END---\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailRfc822Digest($part, &$attachments) {
|
||||
$t = '';
|
||||
$t .= "-DIGEST-ITEM-\n";
|
||||
$t .= "Item:\n\n";
|
||||
$t .= self::formatMailpart($part->mail, $attachments);
|
||||
$t .= "-DIGEST ITEM END-\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailMultipartAlternative($part, &$attachments) {
|
||||
$t = '';
|
||||
foreach ($part->getParts() as $key => $alternativePart) {
|
||||
$t .= "-ALTERNATIVE ITEM $key-\n";
|
||||
$t .= self::formatMailPart($alternativePart, $attachments);
|
||||
}
|
||||
$t .= "-ALTERNATIVE END-\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function formatMailText($part, &$attachments) {
|
||||
$t = "\n{$part->text}\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailMultipartReport($part, &$attachments) {
|
||||
$t = '';
|
||||
foreach ($part->getParts() as $key => $reportPart) {
|
||||
$t .= "-REPORT-$key-\n";
|
||||
$t .= self::formatMailPart($reportPart, $attachments);
|
||||
}
|
||||
$t .= "-REPORT END---\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMailDeliveryStatus($part) {
|
||||
$t = '';
|
||||
$t .= "-DELIVERY STATUS BEGIN-\n";
|
||||
$t .= $part->generateBody();
|
||||
$t .= "-DELIVERY STATUS END-\n";
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatUnrecognisedPart($part) {
|
||||
CRM_Core_Error::debug_log_message(ts('CRM_Utils_Mail_Incoming: Unable to handle message part of type "%1".', array('%1' => get_class($part))));
|
||||
return ts('Unrecognised message part of type "%1".', array('%1' => get_class($part)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
* @param $attachments
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function formatMailFile($part, &$attachments) {
|
||||
$attachments[] = array(
|
||||
'dispositionType' => $part->dispositionType,
|
||||
'contentType' => $part->contentType,
|
||||
'mimeType' => $part->mimeType,
|
||||
'contentID' => $part->contentId,
|
||||
'fullName' => $part->fileName,
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $addresses
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatAddresses($addresses) {
|
||||
$fa = array();
|
||||
foreach ($addresses as $address) {
|
||||
$fa[] = self::formatAddress($address);
|
||||
}
|
||||
return implode(', ', $fa);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $address
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatAddress($address) {
|
||||
$name = '';
|
||||
if (!empty($address->name)) {
|
||||
$name = "{$address->name} ";
|
||||
}
|
||||
return $name . "<{$address->email}>";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function &parse(&$file) {
|
||||
|
||||
// check that the file exists and has some content
|
||||
if (!file_exists($file) ||
|
||||
!trim(file_get_contents($file))
|
||||
) {
|
||||
return CRM_Core_Error::createAPIError(ts('%1 does not exists or is empty',
|
||||
array(1 => $file)
|
||||
));
|
||||
}
|
||||
|
||||
// explode email to digestable format
|
||||
$set = new ezcMailFileSet(array($file));
|
||||
$parser = new ezcMailParser();
|
||||
$mail = $parser->parseMail($set);
|
||||
|
||||
if (!$mail) {
|
||||
return CRM_Core_Error::createAPIError(ts('%1 could not be parsed',
|
||||
array(1 => $file)
|
||||
));
|
||||
}
|
||||
|
||||
// since we only have one fileset
|
||||
$mail = $mail[0];
|
||||
|
||||
$mailParams = self::parseMailingObject($mail);
|
||||
return $mailParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mail
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parseMailingObject(&$mail) {
|
||||
|
||||
$config = CRM_Core_Config::singleton();
|
||||
|
||||
// get ready for collecting data about this email
|
||||
// and put it in a standardized format
|
||||
$params = array('is_error' => 0);
|
||||
|
||||
// Sometimes $mail->from is unset because ezcMail didn't handle format
|
||||
// of From header. CRM-19215.
|
||||
if (!isset($mail->from)) {
|
||||
if (preg_match('/^([^ ]*)( (.*))?$/', $mail->getHeader('from'), $matches)) {
|
||||
$mail->from = new ezcMailAddress($matches[1], trim($matches[2]));
|
||||
}
|
||||
}
|
||||
|
||||
$params['from'] = array();
|
||||
self::parseAddress($mail->from, $field, $params['from'], $mail);
|
||||
|
||||
// we definitely need a contact id for the from address
|
||||
// if we dont have one, skip this email
|
||||
if (empty($params['from']['id'])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$emailFields = array('to', 'cc', 'bcc');
|
||||
foreach ($emailFields as $field) {
|
||||
$value = $mail->$field;
|
||||
self::parseAddresses($value, $field, $params, $mail);
|
||||
}
|
||||
|
||||
// define other parameters
|
||||
$params['subject'] = $mail->subject;
|
||||
$params['date'] = date("YmdHi00",
|
||||
strtotime($mail->getHeader("Date"))
|
||||
);
|
||||
$attachments = array();
|
||||
$params['body'] = self::formatMailPart($mail->body, $attachments);
|
||||
|
||||
// format and move attachments to the civicrm area
|
||||
if (!empty($attachments)) {
|
||||
$date = date('YmdHis');
|
||||
$config = CRM_Core_Config::singleton();
|
||||
for ($i = 0; $i < count($attachments); $i++) {
|
||||
$attachNum = $i + 1;
|
||||
$fileName = basename($attachments[$i]['fullName']);
|
||||
$newName = CRM_Utils_File::makeFileName($fileName);
|
||||
$location = $config->uploadDir . $newName;
|
||||
|
||||
// move file to the civicrm upload directory
|
||||
rename($attachments[$i]['fullName'], $location);
|
||||
|
||||
$mimeType = "{$attachments[$i]['contentType']}/{$attachments[$i]['mimeType']}";
|
||||
|
||||
$params["attachFile_$attachNum"] = array(
|
||||
'uri' => $fileName,
|
||||
'type' => $mimeType,
|
||||
'upload_date' => $date,
|
||||
'location' => $location,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $address
|
||||
* @param array $params
|
||||
* @param $subParam
|
||||
* @param $mail
|
||||
*/
|
||||
public static function parseAddress(&$address, &$params, &$subParam, &$mail) {
|
||||
// CRM-9484
|
||||
if (empty($address->email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subParam['email'] = $address->email;
|
||||
$subParam['name'] = $address->name;
|
||||
|
||||
$contactID = self::getContactID($subParam['email'],
|
||||
$subParam['name'],
|
||||
TRUE,
|
||||
$mail
|
||||
);
|
||||
$subParam['id'] = $contactID ? $contactID : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $addresses
|
||||
* @param $token
|
||||
* @param array $params
|
||||
* @param $mail
|
||||
*/
|
||||
public static function parseAddresses(&$addresses, $token, &$params, &$mail) {
|
||||
$params[$token] = array();
|
||||
|
||||
foreach ($addresses as $address) {
|
||||
$subParam = array();
|
||||
self::parseAddress($address, $params, $subParam, $mail);
|
||||
$params[$token][] = $subParam;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a contact ID and if not present.
|
||||
*
|
||||
* Create one with this email
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param bool $create
|
||||
* @param string $mail
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getContactID($email, $name = NULL, $create = TRUE, &$mail) {
|
||||
$dao = CRM_Contact_BAO_Contact::matchContactOnEmail($email, 'Individual');
|
||||
|
||||
$contactID = NULL;
|
||||
if ($dao) {
|
||||
$contactID = $dao->contact_id;
|
||||
}
|
||||
|
||||
$result = NULL;
|
||||
CRM_Utils_Hook::emailProcessorContact($email, $contactID, $result);
|
||||
|
||||
if (!empty($result)) {
|
||||
if ($result['action'] == self::EMAILPROCESSOR_IGNORE) {
|
||||
return NULL;
|
||||
}
|
||||
if ($result['action'] == self::EMAILPROCESSOR_OVERRIDE) {
|
||||
return $result['contactID'];
|
||||
}
|
||||
|
||||
// else this is now create individual
|
||||
// so we just fall out and do what we normally do
|
||||
}
|
||||
|
||||
if ($contactID) {
|
||||
return $contactID;
|
||||
}
|
||||
|
||||
if (!$create) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// contact does not exist, lets create it
|
||||
$params = array(
|
||||
'contact_type' => 'Individual',
|
||||
'email-Primary' => $email,
|
||||
);
|
||||
|
||||
CRM_Utils_String::extractName($name, $params);
|
||||
|
||||
return CRM_Contact_BAO_Contact::createProfileContact($params,
|
||||
CRM_Core_DAO::$_nullArray
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue