Commit ce31c4e2 authored by catch's avatar catch

Issue #2663270 by Berdir, marthinal, Wim Leers: MailManager::mail() should run...

Issue #2663270 by Berdir, marthinal, Wim Leers: MailManager::mail() should run inside its own render context: it sends e-mails, not (cacheable) responses
parent a03a23b2
......@@ -1259,7 +1259,7 @@ services:
- { name: backend_overridable }
plugin.manager.mail:
class: Drupal\Core\Mail\MailManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation']
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation', '@renderer']
plugin.manager.condition:
class: Drupal\Core\Condition\ConditionManager
parent: default_plugin_manager
......
......@@ -8,6 +8,8 @@
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
......@@ -36,6 +38,13 @@ class MailManager extends DefaultPluginManager implements MailManagerInterface {
*/
protected $loggerFactory;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* List of already instantiated mail plugins.
*
......@@ -59,14 +68,17 @@ class MailManager extends DefaultPluginManager implements MailManagerInterface {
* The logger channel factory.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation) {
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation, RendererInterface $renderer) {
parent::__construct('Plugin/Mail', $namespaces, $module_handler, 'Drupal\Core\Mail\MailInterface', 'Drupal\Core\Annotation\Mail');
$this->alterInfo('mail_backend_info');
$this->setCacheBackend($cache_backend, 'mail_backend_plugins');
$this->configFactory = $config_factory;
$this->loggerFactory = $logger_factory;
$this->stringTranslation = $string_translation;
$this->renderer = $renderer;
}
/**
......@@ -152,6 +164,58 @@ public function getInstance(array $options) {
* {@inheritdoc}
*/
public function mail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) {
// Mailing can invoke rendering (e.g., generating URLs, replacing tokens),
// but e-mails are not HTTP responses: they're not cached, they don't have
// attachments. Therefore we perform mailing inside its own render context,
// to ensure it doesn't leak into the render context for the HTTP response
// to the current request.
return $this->renderer->executeInRenderContext(new RenderContext(), function() use ($module, $key, $to, $langcode, $params, $reply, $send) {
return $this->doMail($module, $key, $to, $langcode, $params, $reply, $send);
});
}
/**
* Composes and optionally sends an email message.
*
* @param string $module
* A module name to invoke hook_mail() on. The {$module}_mail() hook will be
* called to complete the $message structure which will already contain
* common defaults.
* @param string $key
* A key to identify the email sent. The final message ID for email altering
* will be {$module}_{$key}.
* @param string $to
* The email address or addresses where the message will be sent to. The
* formatting of this string will be validated with the
* @link http://php.net/manual/filter.filters.validate.php PHP email validation filter. @endlink
* Some examples are:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
* - User <user@example.com>, Another User <anotheruser@example.com>
* @param string $langcode
* Language code to use to compose the email.
* @param array $params
* (optional) Parameters to build the email.
* @param string|null $reply
* Optional email address to be used to answer.
* @param bool $send
* If TRUE, call an implementation of
* \Drupal\Core\Mail\MailInterface->mail() to deliver the message, and
* store the result in $message['result']. Modules implementing
* hook_mail_alter() may cancel sending by setting $message['send'] to
* FALSE.
*
* @return array
* The $message array structure containing all details of the message. If
* already sent ($send = TRUE), then the 'result' element will contain the
* success indicator of the email, failure being already written to the
* watchdog. (Success means nothing more than the message being accepted at
* php-level, which still doesn't guarantee it to be delivered.)
*
* @see \Drupal\Core\Mail\MailManagerInterface::mail()
*/
public function doMail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) {
$site_config = $this->configFactory->get('system.site');
$site_mail = $site_config->get('mail');
if (empty($site_mail)) {
......
......@@ -10,8 +10,6 @@
* @see \Drupal\Core\Render\RendererInterface
* @see \Drupal\Core\Render\Renderer
* @see \Drupal\Core\Render\BubbleableMetadata
*
* @internal
*/
class RenderContext extends \SplStack {
......
......@@ -6,6 +6,8 @@
namespace Drupal\Tests\Core\Mail;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Mail\MailManager;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
......@@ -44,6 +46,13 @@ class MailManagerTest extends UnitTestCase {
*/
protected $discovery;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $renderer;
/**
* The mail manager under test.
*
......@@ -96,8 +105,9 @@ protected function setUpMailManager($interface = array()) {
));
$logger_factory = $this->getMock('\Drupal\Core\Logger\LoggerChannelFactoryInterface');
$string_translation = $this->getStringTranslationStub();
$this->renderer = $this->getMock(RendererInterface::class);
// Construct the manager object and override its discovery.
$this->mailManager = new TestMailManager(new \ArrayObject(), $this->cache, $this->moduleHandler, $this->configFactory, $logger_factory, $string_translation);
$this->mailManager = new TestMailManager(new \ArrayObject(), $this->cache, $this->moduleHandler, $this->configFactory, $logger_factory, $string_translation, $this->renderer);
$this->mailManager->setDiscovery($this->discovery);
}
......@@ -109,7 +119,7 @@ protected function setUpMailManager($interface = array()) {
public function testGetInstance() {
$interface = array(
'default' => 'php_mail',
'example_testkey' => 'test_mail_collector',
'default' => 'test_mail_collector',
);
$this->setUpMailManager($interface);
......@@ -124,6 +134,28 @@ public function testGetInstance() {
$this->assertInstanceOf('Drupal\Core\Mail\Plugin\Mail\TestMailCollector', $instance);
}
/**
* Tests that mails are sent in a separate render context.
*
* @covers ::mail
*/
public function testMailInRenderContext() {
$interface = array(
'default' => 'php_mail',
'example_testkey' => 'test_mail_collector',
);
$this->setUpMailManager($interface);
$this->renderer->expects($this->exactly(1))
->method('executeInRenderContext')
->willReturnCallback(function (RenderContext $render_context, $callback) {
$message = $callback();
$this->assertEquals('example', $message['module']);
});
$this->mailManager->mail('example', 'key', 'to@example.org', 'en');
}
}
/**
......@@ -140,4 +172,26 @@ public function setDiscovery(DiscoveryInterface $discovery) {
$this->discovery = $discovery;
}
/**
* {@inheritdoc}
*/
public function doMail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) {
// Build a simplified message array and return it.
$message = array(
'id' => $module . '_' . $key,
'module' => $module,
'key' => $key,
'to' => $to,
'from' => 'from@example.org',
'reply-to' => $reply,
'langcode' => $langcode,
'params' => $params,
'send' => TRUE,
'subject' => '',
'body' => array(),
);
return $message;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment