Commit e693a591 authored by willzyx's avatar willzyx

Issue #2832798 by willzyx: Improve DevelMailLog

parent 3e835801
......@@ -3,9 +3,12 @@
namespace Drupal\devel\Plugin\Mail;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Mail\Plugin\Mail\PhpMail;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Mail\MailInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Site\Settings;
use Exception;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a mail backend that saves emails as temporary files.
......@@ -39,10 +42,86 @@ use Exception;
* description = @Translation("Outputs the message as a file in the temporary directory.")
* )
*/
class DevelMailLog extends PhpMail {
class DevelMailLog implements MailInterface, ContainerFactoryPluginInterface {
public function composeMessage($message) {
$mimeheaders = array();
/**
* The devel.settings config object.
*
* @var \Drupal\Core\Config\Config;
*/
protected $config;
/**
* Constructs a new DevelMailLog object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory) {
$this->config = $config_factory->get('devel.settings');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function mail(array $message) {
$directory = $this->config->get('debug_mail_directory');
if (!$this->prepareDirectory($directory)) {
return FALSE;
}
$pattern = $this->config->get('debug_mail_file_format');
$filename = $this->replacePlaceholders($pattern, $message);
$output = $this->composeMessage($message);
return file_put_contents($directory . '/' . $filename, $output);
}
/**
* {@inheritdoc}
*/
public function format(array $message) {
// Join the body array into one string.
$message['body'] = implode("\n\n", $message['body']);
// Convert any HTML to plain-text.
$message['body'] = MailFormatHelper::htmlToText($message['body']);
// Wrap the mail body for sending.
$message['body'] = MailFormatHelper::wrapMail($message['body']);
return $message;
}
/**
* Compose the output message.
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return string
* The output message.
*/
protected function composeMessage($message) {
$mimeheaders = [];
$message['headers']['To'] = $message['to'];
foreach ($message['headers'] as $name => $value) {
$mimeheaders[] = $name . ': ' . Unicode::mimeHeaderEncode($value);
......@@ -58,41 +137,52 @@ class DevelMailLog extends PhpMail {
return $output;
}
public function getFileName($message) {
$output_directory = $this->getOutputDirectory();
$this->makeOutputDirectory($output_directory);
$output_file_format = \Drupal::config('devel.settings')->get('debug_mail_file_format');
$tokens = array(
/**
* Replaces placeholders with sanitized values in a string.
*
* @param $filename
* The string that contains the placeholders. The following placeholders
* are considered in the replacement:
* - %to: replaced by the email recipient value.
* - %subject: replaced by the email subject value.
* - %datetime: replaced by the current datetime in 'y-m-d_his' format.
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return string
* The formatted string.
*/
protected function replacePlaceholders($filename, $message) {
$tokens = [
'%to' => $message['to'],
'%subject' => $message['subject'],
'%datetime' => date('y-m-d_his'),
);
return $output_directory . '/' . $this->dirify(str_replace(array_keys($tokens), array_values($tokens), $output_file_format));
}
private function dirify($string) {
return preg_replace('/[^a-zA-Z0-9_\-\.@]/', '_', $string);
];
$filename = str_replace(array_keys($tokens), array_values($tokens), $filename);
return preg_replace('/[^a-zA-Z0-9_\-\.@]/', '_', $filename);
}
/**
* {@inheritdoc}
* Checks that the directory exists and is writable.
* Public directories will be protected by adding an .htaccess which
* indicates that the directory is private.
*
* @param $directory
* A string reference containing the name of a directory path or URI.
*
* @return bool
* TRUE if the directory exists (or was created), is writable and is
* protected (if it is public). FALSE otherwise.
*/
public function mail(array $message) {
$output = $this->composeMessage($message);
$output_file = $this->getFileName($message);
return file_put_contents($output_file, $output);
}
protected function makeOutputDirectory($output_directory) {
if (!file_prepare_directory($output_directory, FILE_CREATE_DIRECTORY)) {
throw new Exception("Unable to continue sending mail, $output_directory is not writable");
protected function prepareDirectory($directory) {
if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
return FALSE;
}
if (0 === strpos($directory, 'public://')) {
return file_save_htaccess($directory);
}
}
public function getOutputDirectory() {
return \Drupal::config('devel.settings')->get('debug_mail_directory');
return TRUE;
}
}
<?php
namespace Drupal\devel\Tests;
use Drupal\devel\Plugin\Mail\DevelMailLog;
use Drupal\simpletest\WebTestBase;
/**
* Tests sending mails with debug interface.
*
* @group devel
*/
class DevelMailTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('devel');
protected $profile = 'testing';
/**
* Test mail logging functionality.
*/
public function testDevelMail() {
$message = array();
$message['to'] = 'drupal@example.com';
$message['subject'] = 'Test mail';
$message['headers'] = array(
'From' => 'postmaster@example.com',
'X-stupid' => 'dumb',
);
$message['body'] = "I am the body of this message";
$d = new DevelMailLog();
$filename = $d->getFileName($message);
$content = $d->composeMessage($message);
$expected_filename = $d->getOutputDirectory() . '/drupal@example.com-Test_mail-' . date('y-m-d_his') . '.mail.txt';
$this->assertEqual($filename, $expected_filename);
$content = str_replace("\r", '', $content);
$this->assertEqual($content, 'From: postmaster@example.com
X-stupid: dumb
To: drupal@example.com
Subject: Test mail
I am the body of this message');
}
}
<?php
/**
* @file
* Helper module for devel test.
*/
/**
* Implements hook_mail().
*/
function devel_test_mail($key, &$message, $params) {
switch ($key) {
case 'devel_mail_log':
$message['subject'] = $params['subject'];
$message['body'][] = $params['body'];
$message['headers']['From'] = $params['headers']['from'];
$message['headers'] += $params['headers']['additional'];
break;
}
}
<?php
namespace Drupal\Tests\devel\Kernel;
use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
use Drupal\devel\Plugin\Mail\DevelMailLog;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests sending mails with debug interface.
*
* @group devel
*/
class DevelMailLogTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel', 'devel_test', 'system'];
/**
* The mail manager.
*
* @var \Drupal\Core\Mail\MailManagerInterface
*/
protected $mailManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'mail');
$this->installConfig(['system', 'devel']);
// Configure system.site mail settings.
$this->config('system.site')->set('mail', 'devel-test@example.com')->save();
$this->mailManager = $this->container->get('plugin.manager.mail');
}
/**
* Tests devel_mail_log plugin as default mail backend.
*/
public function testDevelMailLogDefaultBackend() {
// Configure devel_mail_log as default mail backends.
$this->setDevelMailLogAsDefaultBackend();
// Ensures that devel_mail_log is the default mail plugin .
$mail_backend = $this->mailManager->getInstance(['module' => 'default', 'key' => 'default']);
$this->assertInstanceOf(DevelMailLog::class, $mail_backend);
$mail_backend = $this->mailManager->getInstance(['module' => 'somemodule', 'key' => 'default']);
$this->assertInstanceOf(DevelMailLog::class, $mail_backend);
}
/**
* Tests devel_mail_log plugin with multiple mail backend.
*/
public function testDevelMailLogMultipleBackend() {
// Configure test_mail_collector as default mail backend.
$this->config('system.mail')
->set('interface.default', 'test_mail_collector')
->save();
// Configure devel_mail_log as a module-specific mail backend.
$this->config('system.mail')
->set('interface.somemodule', 'devel_mail_log')
->save();
// Ensures that devel_mail_log is not the default mail plugin.
$mail_backend = $this->mailManager->getInstance(['module' => 'default', 'key' => 'default']);
$this->assertInstanceOf(TestMailCollector::class, $mail_backend);
// Ensures that devel_mail_log is used as mail backend only for the
// specified module.
$mail_backend = $this->mailManager->getInstance(['module' => 'somemodule', 'key' => 'default']);
$this->assertInstanceOf(DevelMailLog::class, $mail_backend);
}
/**
* Tests devel_mail_log default settings.
*/
public function testDevelMailDefaultSettings() {
$config = \Drupal::config('devel.settings');
$this->assertEquals('temporary://devel-mails', $config->get('debug_mail_directory'));
$this->assertEquals('%to-%subject-%datetime.mail.txt', $config->get('debug_mail_file_format'));
}
/**
* Tests devel mail log output.
*/
public function testDevelMailLogOutput() {
$config = \Drupal::config('devel.settings');
// Parameters used for send the email.
$mail = [
'module' => 'devel_test',
'key' => 'devel_mail_log',
'to' => 'drupal@example.com',
'reply' => 'replyto@example.com',
'lang' => \Drupal::languageManager()->getCurrentLanguage(),
];
// Parameters used for compose the email in devel_test module.
// @see devel_test_mail()
$params = [
'subject' => 'Devel mail log subject',
'body' => 'Devel mail log body',
'headers' => [
'from' => 'postmaster@example.com',
'additional' => [
'X-stupid' => 'dumb',
],
],
];
// Configure devel_mail_log as default mail backends.
$this->setDevelMailLogAsDefaultBackend();
// Changes the default filename pattern removing the dynamic date
// placeholder for a more predictable filename output.
$random = $this->randomMachineName();
$filename_pattern = '%to-%subject-' . $random . '.mail.txt';
$this->config('devel.settings')
->set('debug_mail_file_format', $filename_pattern)
->save();
$expected_filename = 'drupal@example.com-Devel_mail_log_subject-' . $random . '.mail.txt';
$expected_output = <<<EOF
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed; delsp=yes
Content-Transfer-Encoding: 8Bit
X-Mailer: Drupal
Return-Path: devel-test@example.com
Sender: devel-test@example.com
From: postmaster@example.com
Reply-to: replyto@example.com
X-stupid: dumb
To: drupal@example.com
Subject: Devel mail log subject
Devel mail log body
EOF;
// Ensures that the mail is captured by devel_mail_log and the placeholders
// in the filename are properly resolved.
$default_output_directory = $config->get('debug_mail_directory');
$expected_file_path = $default_output_directory . '/' . $expected_filename;
$this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']);
$this->assertFileExists($expected_file_path);
$this->assertStringEqualsFile($expected_file_path, $expected_output);
// Ensures that even changing the default output directory devel_mail_log
// works as expected.
$changed_output_directory = 'temporary://my-folder';
$expected_file_path = $changed_output_directory . '/' . $expected_filename;
$this->config('devel.settings')
->set('debug_mail_directory', $changed_output_directory)
->save();
$this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']);
$this->assertFileExists($expected_file_path);
$this->assertStringEqualsFile($expected_file_path, $expected_output);
// Ensures that if the default output directory is a public directory it
// will be protected by adding an .htaccess.
$public_output_directory = 'public://my-folder';
$expected_file_path = $public_output_directory . '/' . $expected_filename;
$this->config('devel.settings')
->set('debug_mail_directory', $public_output_directory)
->save();
$this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']);
$this->assertFileExists($expected_file_path);
$this->assertStringEqualsFile($expected_file_path, $expected_output);
$this->assertFileExists($public_output_directory . '/.htaccess');
}
/**
* Configure devel_mail_log as default mail backend.
*/
private function setDevelMailLogAsDefaultBackend() {
// TODO can this be avoided?
// KernelTestBase enforce the usage of 'test_mail_collector' plugin for
// collect the mails. Since we need to test devel mail plugin we manually
// configure the mail implementation to use 'devel_mail_log'.
$GLOBALS['config']['system.mail']['interface']['default'] = 'devel_mail_log';
// Configure devel_mail_log as default mail backend.
$this->config('system.mail')
->set('interface.default', 'devel_mail_log')
->save();
}
}
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