}
else {
$mailer = Mail::factory($driver, $params);
- // Previously, CiviCRM bundled patches to change the behavior of these three classes. Use a decorator to avoid patching.
- if ($mailer instanceof Mail_smtp || $mailer instanceof Mail_mail || $mailer instanceof Mail_sendmail) {
- $mailer = new CRM_Utils_Mail_LoggingMailer($mailer);
- }
+ }
+
+ // Previously, CiviCRM bundled patches to change the behavior of 3 specific drivers. Use wrapper/filters to avoid patching.
+ $mailer = new CRM_Utils_Mail_FilteredPearMailer($driver, $params, $mailer);
+ if (in_array($driver, ['smtp', 'mail', 'sendmail'])) {
+ $mailer->addFilter('2000_log', ['CRM_Utils_Mail_Logger', 'filter']);
+ $mailer->addFilter('2100_validate', function ($mailer, &$recipients, &$headers, &$body) {
+ if (!is_array($headers)) {
+ return PEAR::raiseError('$headers must be an array');
+ }
+ });
}
CRM_Utils_Hook::alterMailer($mailer, $driver, $params);
return $mailer;
* @param $to
* @param $headers
* @param $message
+ * @deprecated
*/
public static function logger(&$to, &$headers, &$message) {
- if (is_array($to)) {
- $toString = implode(', ', $to);
- $fileName = $to[0];
- }
- else {
- $toString = $fileName = $to;
- }
- $content = "To: " . $toString . "\n";
- foreach ($headers as $key => $val) {
- $content .= "$key: $val\n";
- }
- $content .= "\n" . $message . "\n";
-
- if (is_numeric(CIVICRM_MAIL_LOG)) {
- $config = CRM_Core_Config::singleton();
- // create the directory if not there
- $dirName = $config->configAndLogDir . 'mail' . DIRECTORY_SEPARATOR;
- CRM_Utils_File::createDir($dirName);
- $fileName = md5(uniqid(CRM_Utils_String::munge($fileName))) . '.txt';
- file_put_contents($dirName . $fileName,
- $content
- );
- }
- else {
- file_put_contents(CIVICRM_MAIL_LOG, $content, FILE_APPEND);
- }
+ CRM_Utils_Mail_Logger::log($to, $headers, $message);
}
/**
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * The filtered-mailer is a utility to wrap an existing PEAR Mail class
+ * and apply extra filters. It is primarily intended for resolving
+ * quirks in the standard implementations.
+ *
+ * This wrapper acts a bit like a chameleon, passing-through properties
+ * from the underlying object. Consequently, internal properties are
+ * prefixed with `_` to avoid conflict.
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+class CRM_Utils_Mail_FilteredPearMailer extends Mail {
+
+ /**
+ * @var string
+ * Ex: 'smtp' or 'sendmail'
+ */
+ protected $_driver;
+
+ /**
+ * @var array
+ */
+ protected $_params;
+
+ /**
+ * @var Mail
+ */
+ protected $_delegate;
+
+ /**
+ * @var callable[]
+ */
+ protected $_filters = [];
+
+ /**
+ * CRM_Utils_Mail_FilteredPearMailer constructor.
+ * @param string $driver
+ * @param array $params
+ * @param Mail $mailer
+ */
+ public function __construct($driver, $params, $mailer) {
+ $this->_driver = $driver;
+ $this->_params = $params;
+ $this->_delegate = $mailer;
+ }
+
+ public function send($recipients, $headers, $body) {
+ $filterArgs = [$this, &$recipients, &$headers, &$body];
+ foreach ($this->_filters as $filter) {
+ $result = call_user_func_array($filter, $filterArgs);
+ if ($result !== NULL) {
+ return $result;
+ }
+ }
+
+ return $this->_delegate->send($recipients, $headers, $body);
+ }
+
+ /**
+ * @param string $id
+ * Unique ID for this filter. Filters are sorted by ID.
+ * Suggestion: '{nnnn}_{name}', where '{nnnn}' is a number.
+ * Filters are sorted and executed in order.
+ * @param callable $func
+ * function(FilteredPearMailer $mailer, mixed $recipients, array $headers, string $body).
+ * The return value should generally be null/void. However, if you wish to
+ * short-circuit execution of the filters, then return a concrete value.
+ * @return static
+ */
+ public function addFilter($id, $func) {
+ $this->_filters[$id] = $func;
+ ksort($this->_filters);
+ return $this;
+ }
+
+ /**
+ * @return string
+ * Ex: 'smtp', 'sendmail', 'mail'.
+ */
+ public function getDriver() {
+ return $this->_driver;
+ }
+
+ public function &__get($name) {
+ return $this->_delegate->{$name};
+ }
+
+ public function __set($name, $value) {
+ return $this->_delegate->{$name} = $value;
+ }
+
+ public function __isset($name) {
+ return isset($this->_delegate->{$name});
+ }
+
+ public function __unset($name) {
+ unset($this->_delegate->{$name});
+ }
+
+}
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * An attachment to PEAR Mail which logs emails to files based on
+ * the CIVICRM_MAIL_LOG configuration.
+ *
+ * (Produced by refactoring; specifically, extracting log-related functions
+ * from CRM_Utils_Mail.)
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+class CRM_Utils_Mail_Logger {
+
+ /**
+ * @param CRM_Utils_Mail_FilteredPearMailer $mailer
+ * @param mixed $recipients
+ * @param array $headers
+ * @param string $body
+ * @return mixed
+ * Normally returns null/void. But if the filter process is to be
+ * short-circuited, then returns a concrete value.
+ */
+ public static function filter($mailer, &$recipients, &$headers, &$body) {
+ if (defined('CIVICRM_MAIL_LOG')) {
+ static::log($recipients, $headers, $body);
+ if (!defined('CIVICRM_MAIL_LOG_AND_SEND') && !defined('CIVICRM_MAIL_LOG_AND SEND')) {
+ return TRUE;
+ }
+ }
+ }
+
+ /**
+ * @param $to
+ * @param $headers
+ * @param $message
+ */
+ public static function log(&$to, &$headers, &$message) {
+ if (is_array($to)) {
+ $toString = implode(', ', $to);
+ $fileName = $to[0];
+ }
+ else {
+ $toString = $fileName = $to;
+ }
+ $content = "To: " . $toString . "\n";
+ foreach ($headers as $key => $val) {
+ $content .= "$key: $val\n";
+ }
+ $content .= "\n" . $message . "\n";
+
+ if (is_numeric(CIVICRM_MAIL_LOG)) {
+ $config = CRM_Core_Config::singleton();
+ // create the directory if not there
+ $dirName = $config->configAndLogDir . 'mail' . DIRECTORY_SEPARATOR;
+ CRM_Utils_File::createDir($dirName);
+ $fileName = md5(uniqid(CRM_Utils_String::munge($fileName))) . '.txt';
+ file_put_contents($dirName . $fileName,
+ $content
+ );
+ }
+ else {
+ file_put_contents(CIVICRM_MAIL_LOG, $content, FILE_APPEND);
+ }
+ }
+
+}
+++ /dev/null
-<?php
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- * The logging-mailer is a utility to wrap an existing PEAR Mail class
- * and apply extra logging functionality.
- *
- * It replaces a set of patches which had been previously applied directly
- * to a few specific PEAR Mail classes.
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- */
-class CRM_Utils_Mail_LoggingMailer extends Mail {
-
- /**
- * @var Mail
- */
- protected $delegate;
-
- /**
- * @param Mail $delegate
- */
- public function __construct($delegate) {
- $this->delegate = $delegate;
- }
-
- public function send($recipients, $headers, $body) {
- if (defined('CIVICRM_MAIL_LOG')) {
- CRM_Utils_Mail::logger($recipients, $headers, $body);
- if (!defined('CIVICRM_MAIL_LOG_AND_SEND') && !defined('CIVICRM_MAIL_LOG_AND SEND')) {
- return TRUE;
- }
- }
-
- if (!is_array($headers)) {
- return PEAR::raiseError('$headers must be an array');
- }
-
- return $this->delegate->send($recipients, $headers, $body);
- }
-
- public function &__get($name) {
- return $this->delegate->{$name};
- }
-
- public function __set($name, $value) {
- return $this->delegate->{$name} = $value;
- }
-
- public function __isset($name) {
- return isset($this->delegate->{$name});
- }
-
- public function __unset($name) {
- unset($this->delegate->{$name});
- }
-
-}
--- /dev/null
+<?php
+
+/**
+ * Class CRM_Utils_Mail_FilteredPearMailerTest
+ * @group headless
+ */
+class CRM_Utils_Mail_FilteredPearMailerTest extends CiviUnitTestCase {
+
+ public function testFilter() {
+ $mock = new class() extends \Mail {
+ public $buf = [];
+
+ public function send($recipients, $headers, $body) {
+ $this->buf['recipients'] = $recipients;
+ $this->buf['headers'] = $headers;
+ $this->buf['body'] = $body;
+ return 'all the fruits in the basket';
+ }
+
+ };
+
+ $fm = new CRM_Utils_Mail_FilteredPearMailer('mock', [], $mock);
+ $fm->addFilter('1000_apple', function ($mailer, &$recipients, &$headers, &$body) {
+ $body .= ' with apples!';
+ });
+ $fm->addFilter('1000_banana', function ($mailer, &$recipients, &$headers, &$body) {
+ $headers['Banana'] = 'Cavendish';
+ });
+ $r = $fm->send(['recip'], ['Subject' => 'Fruit loops'], 'body');
+
+ $this->assertEquals('Fruit loops', $mock->buf['headers']['Subject']);
+ $this->assertEquals('Cavendish', $mock->buf['headers']['Banana']);
+ $this->assertEquals('body with apples!', $mock->buf['body']);
+ $this->assertEquals('all the fruits in the basket', $r);
+ }
+
+ public function testFilter_shortCircuit() {
+ $mock = new class() extends \Mail {
+
+ public function send($recipients, $headers, $body) {
+ return 'all the fruits in the basket';
+ }
+
+ };
+
+ $fm = new CRM_Utils_Mail_FilteredPearMailer('mock', [], $mock);
+ $fm->addFilter('1000_short_circuit', function ($mailer, &$recipients, &$headers, &$body) {
+ return 'the triumph of veggies over fruits';
+ });
+ $r = $fm->send(['recip'], ['Subject' => 'Fruit loops'], 'body');
+ $this->assertEquals('the triumph of veggies over fruits', $r);
+ }
+
+}