Skip to content

Commit

Permalink
Merge pull request #16497 from totten/master-pear-mail-delg
Browse files Browse the repository at this point in the history
(REF) Move CIVICRM_MAIL_LOG logic from patch-files to wrapper-class
  • Loading branch information
seamuslee001 authored Feb 11, 2020
2 parents 3c90fbb + e4c7508 commit ead59d8
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 81 deletions.
2 changes: 1 addition & 1 deletion CRM/Admin/Form/Setting/Smtp.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public function postProcess() {
'Subject' => $subject,
];

$mailer = Mail::factory($mailerName, $params);
$mailer = CRM_Utils_Mail::_createMailer($mailerName, $params);

$errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
$result = $mailer->send($toEmail, $headers, $message);
Expand Down
44 changes: 17 additions & 27 deletions CRM/Utils/Mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ public static function _createMailer($driver, $params) {
else {
$mailer = Mail::factory($driver, $params);
}

// 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;
}
Expand Down Expand Up @@ -268,7 +279,10 @@ public static function send(&$params) {
// * All other mailers require that all be recipients be listed in the $to array AND that
// the Bcc must not be present in $header as otherwise it will be shown to all recipients
// ref: https://pear.php.net/bugs/bug.php?id=8047, full thread and answer [2011-04-19 20:48 UTC]
if (get_class($mailer) != "Mail_mail") {
// TODO: Refactor this quirk-handler as another filter in FilteredPearMailer. But that would merit review of impact on universe.
$driver = ($mailer instanceof CRM_Utils_Mail_FilteredPearMailer) ? $mailer->getDriver() : NULL;
$isPhpMail = (get_class($mailer) === "Mail_mail" || $driver === 'mail');
if (!$isPhpMail) {
// get emails from headers, since these are
// combination of name and email addresses.
if (!empty($headers['Cc'])) {
Expand Down Expand Up @@ -326,34 +340,10 @@ public static function errorMessage($mailer, $result) {
* @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);
}

/**
Expand Down
112 changes: 112 additions & 0 deletions CRM/Utils/Mail/FilteredPearMailer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?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});
}

}
76 changes: 76 additions & 0 deletions CRM/Utils/Mail/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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);
}
}

}
54 changes: 54 additions & 0 deletions tests/phpunit/CRM/Utils/Mail/FilteredPearMailerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?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);
}

}
53 changes: 0 additions & 53 deletions tools/scripts/composer/patches/pear-mail.patch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,6 @@ diff --git a/Mail/mail.php b/Mail/mail.php
index ee1ecef..ae6e2e8 100644
--- a/Mail/mail.php
+++ b/Mail/mail.php
@@ -114,6 +114,14 @@ class Mail_mail extends Mail {
*/
public function send($recipients, $headers, $body)
{
+ if (defined('CIVICRM_MAIL_LOG')) {
+ CRM_Utils_Mail::logger($recipients, $headers, $body);
+ // Note: "CIVICRM_MAIL_LOG_AND SEND" (space not underscore) was a typo that existed for some years, so kept here for compatibility, but it should not be used.
+ 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');
}
@@ -145,7 +153,12 @@ class Mail_mail extends Mail {
if (is_a($headerElements, 'PEAR_Error')) {
return $headerElements;
Expand All @@ -46,41 +31,3 @@ index ee1ecef..ae6e2e8 100644

// We only use mail()'s optional fifth parameter if the additional
// parameters have been provided and we're not running in safe mode.
diff --git a/Mail/sendmail.php b/Mail/sendmail.php
index 7e8f804..e0300a0 100644
--- a/Mail/sendmail.php
+++ b/Mail/sendmail.php
@@ -132,6 +132,14 @@ class Mail_sendmail extends Mail {
*/
public function send($recipients, $headers, $body)
{
+ if (defined('CIVICRM_MAIL_LOG')) {
+ CRM_Utils_Mail::logger($recipients, $headers, $body);
+ // Note: "CIVICRM_MAIL_LOG_AND SEND" (space not underscore) was a typo that existed for some years, so kept here for compatibility, but it should not be used.
+ 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');
}
diff --git a/Mail/smtp.php b/Mail/smtp.php
index 5e698fe..5f057e2 100644
--- a/Mail/smtp.php
+++ b/Mail/smtp.php
@@ -255,6 +255,14 @@ class Mail_smtp extends Mail {
*/
public function send($recipients, $headers, $body)
{
+ if (defined('CIVICRM_MAIL_LOG')) {
+ CRM_Utils_Mail::logger($recipients, $headers, $body);
+ // Note: "CIVICRM_MAIL_LOG_AND SEND" (space not underscore) was a typo that existed for some years, so kept here for compatibility, but it should not be used.
+ if (!defined('CIVICRM_MAIL_LOG_AND_SEND') && !defined('CIVICRM_MAIL_LOG_AND SEND')) {
+ return true;
+ }
+ }
+
$result = $this->send_or_fail($recipients, $headers, $body);

/* If persistent connections are disabled, destroy our SMTP object. */

0 comments on commit ead59d8

Please sign in to comment.