/** * Return the proper mail store implementation, based on config settings * * @param string $name name of the settings set from civimail_mail_settings to use (null for default) * @return object mail store implementation for processing CiviMail-bound emails */ function getStore($name = null) { $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $name ? $dao->name = $name : ($dao->is_default = 1); if (!$dao->find(true)) { throw new Exception("Could not find entry named {$name} in civicrm_mail_settings"); } $protocols =& CRM_Core_PseudoConstant::mailProtocol(); switch ($protocols[$dao->protocol]) { case 'IMAP': require_once 'CRM/Mailing/MailStore/Imap.php'; return new CRM_Mailing_MailStore_Imap($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl, $dao->source); case 'POP3': require_once 'CRM/Mailing/MailStore/Pop3.php'; return new CRM_Mailing_MailStore_Pop3($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl); case 'Maildir': require_once 'CRM/Mailing/MailStore/Maildir.php'; return new CRM_Mailing_MailStore_Maildir($dao->source); case 'Localdir': require_once 'CRM/Mailing/MailStore/Localdir.php'; return new CRM_Mailing_MailStore_Localdir($dao->source); // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards case 'mbox': require_once 'CRM/Mailing/MailStore/Mbox.php'; return new CRM_Mailing_MailStore_Mbox($dao->source); default: throw new Exception("Unknown protocol {$dao->protocol}"); } }
/** * Browse all mail settings. * * @return void */ public function browse() { //get all mail settings. $allMailSettings = array(); $mailSetting = new CRM_Core_DAO_MailSettings(); $allProtocols = CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol'); //multi-domain support for mail settings. CRM-5244 $mailSetting->domain_id = CRM_Core_Config::domainID(); //find all mail settings. $mailSetting->find(); while ($mailSetting->fetch()) { //replace protocol value with name $mailSetting->protocol = CRM_Utils_Array::value($mailSetting->protocol, $allProtocols); CRM_Core_DAO::storeValues($mailSetting, $allMailSettings[$mailSetting->id]); //form all action links $action = array_sum(array_keys($this->links())); // disallow the DELETE action for the default set of settings if ($mailSetting->is_default) { $action &= ~CRM_Core_Action::DELETE; } //add action links. $allMailSettings[$mailSetting->id]['action'] = CRM_Core_Action::formLink(self::links(), $action, array('id' => $mailSetting->id), ts('more'), FALSE, 'mailSetting.manage.action', 'MailSetting', $mailSetting->id); } $this->assign('rows', $allMailSettings); }
/** * Return the proper mail store implementation, based on config settings * * @param string $name * Name of the settings set from civimail_mail_settings to use (null for default). * * @throws Exception * @return object * mail store implementation for processing CiviMail-bound emails */ public static function getStore($name = NULL) { $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $name ? $dao->name = $name : ($dao->is_default = 1); if (!$dao->find(TRUE)) { throw new Exception("Could not find entry named {$name} in civicrm_mail_settings"); } $protocols = CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol'); if (empty($protocols[$dao->protocol])) { throw new Exception("Empty mail protocol"); } switch ($protocols[$dao->protocol]) { case 'IMAP': return new CRM_Mailing_MailStore_Imap($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl, $dao->source); case 'POP3': return new CRM_Mailing_MailStore_Pop3($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl); case 'Maildir': return new CRM_Mailing_MailStore_Maildir($dao->source); case 'Localdir': return new CRM_Mailing_MailStore_Localdir($dao->source); // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards case 'mbox': return new CRM_Mailing_MailStore_Mbox($dao->source); default: throw new Exception("Unknown protocol {$dao->protocol}"); } }
/** * Return the proper mail store implementation, based on config settings * * @param string $name name of the settings set from civimail_mail_settings to use (null for default) * * @return object mail store implementation for processing CiviMail-bound emails */ function getStore($name = NULL) { $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $name ? $dao->name = $name : ($dao->is_default = 1); if (!$dao->find(TRUE)) { throw new Exception("Could not find entry named {$name} in civicrm_mail_settings"); } $protocols = CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol'); switch ($protocols[$dao->protocol]) { case 'IMAP': return new CRM_Mailing_MailStore_Imap($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl, $dao->source); case 'POP3': return new CRM_Mailing_MailStore_Pop3($dao->server, $dao->username, $dao->password, (bool) $dao->is_ssl); case 'Maildir': return new CRM_Mailing_MailStore_Maildir($dao->source); case 'Localdir': return new CRM_Mailing_MailStore_Localdir($dao->source); // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards // DO NOT USE the mbox transport for anything other than testing // in particular, it does not clear the mbox afterwards case 'mbox': return new CRM_Mailing_MailStore_Mbox($dao->source); default: $class_name = 'CRM_Mailing_MailStore_' . $protocols[$dao->protocol]; /* * Whilst this may seem the logical way to test for the class it will cause errors if it does not exist * This is because the autoloader will try and require_once the classname without checking for the existance of the file * This still seems the best way to implement this as if this falls back then the app will throw an exception anyway * In an ideal world the autoloader would also throw an exception if the file does not exist which could have been caught here */ if (class_exists($class_name)) { return new $class_name($dao->username, $dao->password); } throw new Exception("Unknown protocol {$dao->protocol}"); } }
/** * 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); } }
/** * Delete the mail settings. * * @param int $id * Mail settings id. * * @return mixed|null */ public static function deleteMailSettings($id) { $results = NULL; $transaction = new CRM_Core_Transaction(); $mailSettings = new CRM_Core_DAO_MailSettings(); $mailSettings->id = $id; $results = $mailSettings->delete(); $transaction->commit(); return $results; }
/** * Returns the list of fields that can be exported * * @param bool $prefix * * @return array */ static function &export($prefix = false) { if (!self::$_export) { self::$_export = array(); $fields = self::fields(); foreach ($fields as $name => $field) { if (CRM_Utils_Array::value('export', $field)) { if ($prefix) { self::$_export['mail_settings'] =& $fields[$name]; } else { self::$_export[$name] =& $fields[$name]; } } } } return self::$_export; }
/** * Process the mailbox for all the settings from civicrm_mail_settings * * @param string $civiMail if true, processing is done in CiviMail context, or Activities otherwise. * @return void */ static function process($civiMail = true) { require_once 'CRM/Core/DAO/MailSettings.php'; $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $dao->find(); while ($dao->fetch()) { EmailProcessor::_process($civiMail, $dao); } }
/** * Process the mailbox defined by the named set of settings from civicrm_mail_settings * * @param string $name name of the set of settings from civicrm_mail_settings (null for default set) * @return void */ static function process($name = null) { require_once 'CRM/Core/DAO/MailSettings.php'; $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $name ? $dao->name = $name : ($dao->is_default = 1); if (!$dao->find(true)) { throw new Exception("Could not find entry named {$name} in civicrm_mail_settings"); } $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: ' . preg_quote($dao->localpart) . '(b)' . $twoDigitString . '([0-9a-f]{16})@' . preg_quote($dao->domain) . '/'; // retrieve the emails require_once 'CRM/Mailing/MailStore.php'; $store = CRM_Mailing_MailStore::getStore($name); require_once 'api/Mailer.php'; // process fifty at a time, CRM-4002 while ($mails = $store->fetchNext(50)) { foreach ($mails as $key => $mail) { // for every addressee: match address elements if it's to CiviMail $matches = array(); 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 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 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 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; } } } } crm_mailer_event_bounce($job, $queue, $hash, $text); break; case 'c': case 'confirm': crm_mailer_event_confirm($job, $queue, $hash); break; case 'o': case 'optOut': crm_mailer_event_domain_unsubscribe($job, $queue, $hash); break; case 'r': case 'reply': // instead of text and HTML parts (4th and 6th params) send the whole email as the last param crm_mailer_event_reply($job, $queue, $hash, null, $replyTo, null, $mail->generate()); break; case 'e': case 're': case 'resubscribe': crm_mailer_event_resubscribe($job, $queue, $hash); break; case 's': case 'subscribe': crm_mailer_event_subscribe($mail->from->email, $job); break; case 'u': case 'unsubscribe': crm_mailer_event_unsubscribe($job, $queue, $hash); break; } $store->markProcessed($key); } } }
/** * Function to delete the mail settings. * * @param int $id mail settings id * * @access public * @static * */ static function deleteMailSettings($id) { $results = null; require_once 'CRM/Core/Transaction.php'; $transaction = new CRM_Core_Transaction(); $mailSettings = new CRM_Core_DAO_MailSettings(); $mailSettings->id = $id; $results = $mailSettings->delete(); $transaction->commit(); return $results; }
/** * Process the mailbox for all the settings from civicrm_mail_settings * * @param string $civiMail if true, processing is done in CiviMail context, or Activities otherwise. * @return void */ static function process($civiMail = true) { require_once 'CRM/Core/OptionGroup.php'; $emailActivityTypeId = defined('EMAIL_ACTIVITY_TYPE_ID') && EMAIL_ACTIVITY_TYPE_ID ? EMAIL_ACTIVITY_TYPE_ID : CRM_Core_OptionGroup::getValue('activity_type', 'Inbound Email', 'name'); if (!$emailActivityTypeId) { CRM_Core_Error::fatal(ts('Could not find a valid Activity Type ID for Inbound Email')); } require_once 'CRM/Core/DAO/MailSettings.php'; $dao = new CRM_Core_DAO_MailSettings(); $dao->domain_id = CRM_Core_Config::domainID(); $dao->find(); while ($dao->fetch()) { // 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)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.([0-9a-f]{16})(-.*)?@' . preg_quote($dao->domain) . '$/'; $subscrRegex = '/^' . preg_quote($dao->localpart) . '(s|subscribe)\\.(\\d+)\\.(\\d+)@' . 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)\\.(\\d+)\\.(\\d+)\\.([0-9a-f]{16})@' . preg_quote($dao->domain) . '$/'; // retrieve the emails require_once 'CRM/Mailing/MailStore.php'; $store = CRM_Mailing_MailStore::getStore($dao->name); require_once 'api/Mailer.php'; // 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(); if ($civiMail) { 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; } } } else { // if its the activities that needs to be processed .. require_once 'CRM/Utils/Mail/Incoming.php'; $mailParams = CRM_Utils_Mail_Incoming::parseMailingObject($mail); require_once 'api/v2/Activity.php'; $params = _civicrm_activity_buildmailparams($mailParams, $emailActivityTypeId); $result = civicrm_activity_create($params); if ($result['is_error']) { $matches = false; echo "Failed Processing: {$mail->subject}. Reason: {$result['error_message']}\n"; } else { $matches = true; echo "Processed: {$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)) { switch ($action) { case 'b': case 'bounce': $text = ''; if ($mail->body instanceof ezcMailText) { $text = $mail->body->text; } elseif ($mail->body instanceof ezcMailMultipart) { foreach ($mail->body->getParts() as $part) { if (isset($part->subType) and $part->subType == 'plain') { $text = $part->text; break; } } } crm_mailer_event_bounce($job, $queue, $hash, $text); break; case 'c': case 'confirm': crm_mailer_event_confirm($job, $queue, $hash); break; case 'o': case 'optOut': crm_mailer_event_domain_unsubscribe($job, $queue, $hash); break; case 'r': case 'reply': // instead of text and HTML parts (4th and 6th params) send the whole email as the last param crm_mailer_event_reply($job, $queue, $hash, null, $replyTo, null, $mail->generate()); break; case 'e': case 're': case 'resubscribe': crm_mailer_event_resubscribe($job, $queue, $hash); break; case 's': case 'subscribe': crm_mailer_event_subscribe($mail->from->email, $job); break; case 'u': case 'unsubscribe': crm_mailer_event_unsubscribe($job, $queue, $hash); break; } } $store->markProcessed($key); } } } }