From 16feff8e8d8f202001c512e46e127cb3eaab5c3c Mon Sep 17 00:00:00 2001 From: Dave Long <dave@longwaveconsulting.com> Date: Thu, 19 Oct 2023 16:54:31 +0200 Subject: [PATCH] Issue #3165762 by znerol, Berdir, jungle, AdamPS, longwave, smustgrave, imclean, catch, dpi: Add symfony/mailer into core --- composer.json | 1 + composer.lock | 82 ++++++++- .../Metapackage/DevDependencies/composer.json | 1 + .../PinnedDevDependencies/composer.json | 1 + core/core.services.yml | 3 + .../Core/Mail/Plugin/Mail/SymfonyMailer.php | 164 ++++++++++++++++++ .../Core/Test/FunctionalTestSetupTrait.php | 1 + .../MigrateUpgradeExecuteTestBase.php | 4 + .../system/config/install/system.mail.yml | 1 + .../system/config/schema/system.schema.yml | 3 + .../system/migrations/d7_system_mail.yml | 6 + core/modules/system/system.post_update.php | 8 + .../Update/MailDsnSettingsUpdateTest.php | 36 ++++ .../d7/MigrateSystemConfigurationTest.php | 1 + .../Installer/InstallerTestBase.php | 1 + .../Drupal/KernelTests/KernelTestBase.php | 1 + .../Tests/Core/Mail/MailManagerTest.php | 1 + .../Core/Mail/Plugin/Mail/PhpMailTest.php | 1 + .../Mail/Plugin/Mail/SymfonyMailerTest.php | 141 +++++++++++++++ 19 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php create mode 100644 core/modules/system/tests/src/Functional/Update/MailDsnSettingsUpdateTest.php create mode 100644 core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/SymfonyMailerTest.php diff --git a/composer.json b/composer.json index e551ea3eafc2..3242b5e5fee5 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "symfony/filesystem": "^6.3", "symfony/finder": "^6.3", "symfony/lock": "^6.3", + "symfony/mailer": "^6.3", "symfony/phpunit-bridge": "^6.3", "symfony/var-dumper": "^6.3" }, diff --git a/composer.lock b/composer.lock index 18e4389daf98..45f224e5e158 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1f03bd567e9e6a240f6bec2e76b7f448", + "content-hash": "93842a6c87cec9f144cd50dab29818e7", "packages": [ { "name": "asm89/stack-cors", @@ -9138,6 +9138,86 @@ ], "time": "2023-04-21T12:19:45+00:00" }, + { + "name": "symfony/mailer", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d89611a7830d51b5e118bca38e390dea92f9ea06", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/twig-bridge": "^6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.3.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-09-06T09:47:15+00:00" + }, { "name": "symfony/phpunit-bridge", "version": "v6.3.0", diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json index 0dae0f6f6013..d67c501890ac 100644 --- a/composer/Metapackage/DevDependencies/composer.json +++ b/composer/Metapackage/DevDependencies/composer.json @@ -33,6 +33,7 @@ "symfony/filesystem": "^6.3", "symfony/finder": "^6.3", "symfony/lock": "^6.3", + "symfony/mailer": "^6.3", "symfony/phpunit-bridge": "^6.3", "symfony/var-dumper": "^6.3" } diff --git a/composer/Metapackage/PinnedDevDependencies/composer.json b/composer/Metapackage/PinnedDevDependencies/composer.json index 7b3931e62c46..35e44726d472 100644 --- a/composer/Metapackage/PinnedDevDependencies/composer.json +++ b/composer/Metapackage/PinnedDevDependencies/composer.json @@ -86,6 +86,7 @@ "symfony/filesystem": "v6.3.0", "symfony/finder": "v6.3.0", "symfony/lock": "v6.3.0", + "symfony/mailer": "v6.3.5", "symfony/phpunit-bridge": "v6.3.0", "symfony/polyfill-php82": "v1.27.0", "theseer/tokenizer": "1.2.1", diff --git a/core/core.services.yml b/core/core.services.yml index 0b093c466998..8e2439ed9e12 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -500,6 +500,9 @@ services: logger.channel.security: parent: logger.channel_base arguments: ['security'] + logger.channel.mail: + parent: logger.channel_base + arguments: ['mail'] logger.channel.menu: parent: logger.channel_base arguments: ['menu'] diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php new file mode 100644 index 000000000000..f99fb86a8f6d --- /dev/null +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php @@ -0,0 +1,164 @@ +<?php + +namespace Drupal\Core\Mail\Plugin\Mail; + +use Drupal\Component\Render\MarkupInterface; +use Drupal\Core\Mail\MailFormatHelper; +use Drupal\Core\Mail\MailInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Utility\Error; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport; +use Symfony\Component\Mime\Email; + +/** + * Defines an experimental mail backend, based on the Symfony mailer component. + * + * This mail plugin acts as a drop-in replacement for the current default PHP + * mail plugin. Mail delivery is based on the Symfony mailer component. Hence, + * all transports registered by default in the Symfony mailer transport factory + * are available via configurable DSN. + * + * By default, this plugin uses `sendmail://default` as the transport DSN. I.e., + * it attempts to use `/usr/sbin/sendmail -bs` in order to submit a message to + * the MTA. Sites hosted on operating systems without a working MTA (e.g., + * Windows) need to configure a suitable DSN. + * + * The DSN can be set via the `mailer_dsn` key of the `system.mailer` config. + * + * The following example shows how to switch the default mail plugin to the + * experimental Symfony mailer plugin with a custom DSN using config overrides + * in `settings.php`: + * + * @code + * $config['system.mail']['interface'] = [ 'default' => 'symfony_mailer' ]; + * $config['system.mail']['mailer_dsn'] = 'smtp://user:pass@smtp.example.com:25'; + * @endcode + * + * Note that special characters in the mailer_dsn need to be URL encoded. + * + * @see https://symfony.com/doc/current/mailer.html#using-built-in-transports + * + * @Mail( + * id = "symfony_mailer", + * label = @Translation("Symfony mailer (Experimental)"), + * ) + * + * @internal + */ +class SymfonyMailer implements MailInterface, ContainerFactoryPluginInterface { + + /** + * A list of headers that can contain multiple email addresses. + * + * @see \Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP + */ + protected const MAILBOX_LIST_HEADERS = ['from', 'to', 'reply-to', 'cc', 'bcc']; + + /** + * List of headers to skip copying from the message array. + * + * Symfony mailer sets Content-Type and Content-Transfer-Encoding according to + * the actual body content. Note that format=flowed is not supported by + * Symfony. + * + * @see \Symfony\Component\Mime\Part\TextPart + */ + protected const SKIP_HEADERS = ['content-type', 'content-transfer-encoding']; + + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('logger.channel.mail') + ); + } + + /** + * Symfony mailer constructor. + * + * @param \Psr\Log\LoggerInterface $logger + * The logger service. + * @param \Symfony\Component\Mailer\MailerInterface $mailer + * The mailer service. Only specify an instance in unit tests, pass NULL in + * production. + */ + public function __construct( + protected LoggerInterface $logger, + protected ?MailerInterface $mailer = NULL) { + } + + public function format(array $message) { + // Convert any HTML to plain-text. + foreach ($message['body'] as &$part) { + if ($part instanceof MarkupInterface) { + $part = MailFormatHelper::htmlToText($part); + } + else { + $part = MailFormatHelper::wrapMail($part); + } + } + + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + + return $message; + } + + public function mail(array $message) { + try { + $email = new Email(); + + $headers = $email->getHeaders(); + foreach ($message['headers'] as $name => $value) { + if (!in_array(strtolower($name), self::SKIP_HEADERS, TRUE)) { + if (in_array(strtolower($name), self::MAILBOX_LIST_HEADERS, TRUE)) { + // Split values by comma, but ignore commas encapsulated in double + // quotes. + $value = str_getcsv($value, ','); + } + $headers->addHeader($name, $value); + } + } + + $email + ->to($message['to']) + ->subject($message['subject']) + ->text($message['body']); + + $mailer = $this->getMailer(); + $mailer->send($email); + return TRUE; + } + catch (\Exception $e) { + Error::logException($this->logger, $e); + return FALSE; + } + } + + /** + * Returns a minimalistic Symfony mailer service. + */ + protected function getMailer(): MailerInterface { + if (!isset($this->mailer)) { + $dsn = \Drupal::config('system.mail')->get('mailer_dsn'); + + // Symfony Mailer and Transport classes both optionally depend on the + // event dispatcher. When provided, a MessageEvent is fired whenever an + // email is prepared before sending. + // + // The MessageEvent will likely play an important role in an upcoming mail + // API. However, emails handled by this plugin already were processed by + // hook_mail and hook_mail_alter. Firing the MessageEvent would leak those + // mails into the code path (i.e., event subscribers) of the new API. + // Therefore, this plugin deliberately refrains from injecting the event + // dispatcher. + $transport = Transport::fromDsn($dsn, logger: $this->logger); + $this->mailer = new Mailer($transport); + } + + return $this->mailer; + } + +} diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php index c4014bf27bd1..9985fffde337 100644 --- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php +++ b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php @@ -327,6 +327,7 @@ protected function initConfig(ContainerInterface $container) { // some tests expect to be able to test mail system implementations. $config->getEditable('system.mail') ->set('interface.default', 'test_mail_collector') + ->set('mailer_dsn', 'null://null') ->save(); // By default, verbosely display all errors and disable all production diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php index 3f1d0276a437..10d6305d24b3 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php @@ -65,6 +65,10 @@ public function useTestMailCollector() { 'value' => 'test_mail_collector', 'required' => TRUE, ]; + $settings['config']['system.mail']['mailer_dsn'] = (object) [ + 'value' => 'null://null', + 'required' => TRUE, + ]; $this->writeSettings($settings); } diff --git a/core/modules/system/config/install/system.mail.yml b/core/modules/system/config/install/system.mail.yml index 0ea09c3812e5..67c1e29f92a4 100644 --- a/core/modules/system/config/install/system.mail.yml +++ b/core/modules/system/config/install/system.mail.yml @@ -1,2 +1,3 @@ interface: default: 'php_mail' +mailer_dsn: "sendmail://default" diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index c0da210a39a1..11301fb9f520 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -302,6 +302,9 @@ system.mail: sequence: type: string label: 'Interface' + mailer_dsn: + type: string + label: 'Symfony mailer transport DSN' system.theme.global: type: theme_settings diff --git a/core/modules/system/migrations/d7_system_mail.yml b/core/modules/system/migrations/d7_system_mail.yml index 5bb46f4941d4..d2038043e925 100644 --- a/core/modules/system/migrations/d7_system_mail.yml +++ b/core/modules/system/migrations/d7_system_mail.yml @@ -15,6 +15,12 @@ process: map: DefaultMailSystem: php_mail MailTestCase: test_mail_collector + 'mailer_dsn': + plugin: static_map + source: 'mail_system/default-system' + map: + DefaultMailSystem: 'sendmail://default' + MailTestCase: 'null://null' destination: plugin: config config_name: system.mail diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 14fd7bfc01ba..bdd6916fa6f0 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -158,3 +158,11 @@ function system_post_update_set_blank_log_url_to_null() { ->save(TRUE); } } + +/** + * Add new default mail transport dsn. + */ +function system_post_update_mailer_dsn_settings() { + $config = \Drupal::configFactory()->getEditable('system.mail'); + $config->set('mailer_dsn', 'sendmail://default')->save(); +} diff --git a/core/modules/system/tests/src/Functional/Update/MailDsnSettingsUpdateTest.php b/core/modules/system/tests/src/Functional/Update/MailDsnSettingsUpdateTest.php new file mode 100644 index 000000000000..42542cf46db8 --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/MailDsnSettingsUpdateTest.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\Tests\system\Functional\Update; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; + +/** + * Tests creation of default mail transport dsn settings. + * + * @see system_post_update_mailer_dsn_settings() + * + * @group Update + */ +class MailDsnSettingsUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../fixtures/update/drupal-9.4.0.bare.standard.php.gz', + ]; + } + + /** + * Tests system_post_update_mailer_dsn_settings(). + */ + public function testSystemPostUpdateMailerDsnSettings() { + $this->runUpdates(); + + // Confirm that config was created. + $config = $this->config('system.mail'); + $this->assertEquals('sendmail://default', $config->get('mailer_dsn')); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php index 0d3b2fafd053..6af738f5251e 100644 --- a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php +++ b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php @@ -59,6 +59,7 @@ class MigrateSystemConfigurationTest extends MigrateDrupal7TestBase { 'interface' => [ 'default' => 'php_mail', ], + 'mailer_dsn' => 'sendmail://default', ], 'system.maintenance' => [ // langcode is not handled by the migration. diff --git a/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php b/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php index c750816d1b6c..8bd28ac0e966 100644 --- a/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php +++ b/core/tests/Drupal/FunctionalTests/Installer/InstallerTestBase.php @@ -197,6 +197,7 @@ protected function setUp(): void { $this->container->get('config.factory') ->getEditable('system.mail') ->set('interface.default', 'test_mail_collector') + ->set('mailer_dsn', 'null://null') ->save(); $this->installDefaultThemeFromClassProperty($this->container); diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index 5544dc324708..9a0702b28c3c 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -430,6 +430,7 @@ protected function bootKernel() { // While this should be enforced via settings.php prior to installation, // some tests expect to be able to test mail system implementations. $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector'; + $GLOBALS['config']['system.mail']['mailer_dsn'] = 'null://null'; // Manually configure the default file scheme so that modules that use file // functions don't have to install system and its configuration. diff --git a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php index b87260d5673a..3c2004a7b614 100644 --- a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php @@ -119,6 +119,7 @@ protected function setUpMailManager($interface = []) { $this->configFactory = $this->getConfigFactoryStub([ 'system.mail' => [ 'interface' => $interface, + 'mailer_dsn' => 'null://null', ], 'system.site' => [ 'mail' => 'test@example.com', diff --git a/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/PhpMailTest.php b/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/PhpMailTest.php index fb4006e6f93a..ec216092c7af 100644 --- a/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/PhpMailTest.php +++ b/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/PhpMailTest.php @@ -46,6 +46,7 @@ protected function setUp(): void { $this->configFactory = $this->getConfigFactoryStub([ 'system.mail' => [ 'interface' => [], + 'mailer_dsn' => 'null://null', ], 'system.site' => [ 'mail' => 'test@example.com', diff --git a/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/SymfonyMailerTest.php b/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/SymfonyMailerTest.php new file mode 100644 index 000000000000..99ec411a754f --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/SymfonyMailerTest.php @@ -0,0 +1,141 @@ +<?php + +namespace Drupal\Tests\Core\Mail\Plugin\Mail; + +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Mail\MailFormatHelper; +use Drupal\Core\Mail\Plugin\Mail\SymfonyMailer; +use Drupal\Tests\UnitTestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Email; + +/** + * @coversDefaultClass \Drupal\Core\Mail\Plugin\Mail\SymfonyMailer + * @group Mail + */ +class SymfonyMailerTest extends UnitTestCase { + + /** + * Tests that mixed plain text and html body is converted correctly. + * + * @covers ::format + */ + public function testFormatResemblesHtml() { + // Populate global $base_path to avoid notices generated by + // MailFormatHelper::htmlToMailUrls() + global $base_path; + $original_base_path = $base_path; + $base_path = '/'; + + $variables = [ + '@form-url' => 'https://www.example.com/contact', + '@sender-url' => 'https://www.example.com/user/123', + '@sender-name' => $this->randomString(), + ]; + + $plain = "In HTML, ampersand must be written as &.\nI saw your house and <wow> it is great. There is too much to say about that beautiful building, it will never fit on one line of text.\nIf a<b and b<c then a<c."; + $template = "@sender-name (@sender-url) sent a message using the contact form at @form-url."; + $markup = new FormattableMarkup($template, $variables); + + $message = [ + 'body' => [ + $plain, + $markup, + ], + ]; + + /** @var \Symfony\Component\Mailer\MailerInterface|\PHPUnit\Framework\MockObject\MockObject */ + $mailer = $this->getMockBuilder(MailerInterface::class)->getMock(); + + /** @var \Psr\Log\LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + + $plugin = new SymfonyMailer($logger, $mailer); + $message = $plugin->format($message); + + $expect = MailFormatHelper::wrapMail($plain . "\n\n" . strtr($template, $variables) . "\n"); + $this->assertEquals($expect, $message['body']); + + $base_path = $original_base_path; + } + + /** + * Tests sending a mail using a From address with a comma in it. + * + * @covers ::mail + */ + public function testMail() { + // Setup a mail message. + $message = [ + 'id' => 'example_key', + 'module' => 'example', + 'key' => 'key', + 'to' => 'to@example.org', + 'from' => 'from@example.org', + 'reply-to' => 'from@example.org', + 'langcode' => 'en', + 'params' => [], + 'send' => TRUE, + 'subject' => "test\r\nsubject", + 'body' => '', + 'headers' => [ + 'MIME-Version' => '1.0', + 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes', + 'Content-Transfer-Encoding' => '8Bit', + 'X-Mailer' => 'Drupal', + 'From' => '"Foo, Bar, and Baz" <from@example.org>', + 'Reply-to' => 'from@example.org', + 'Return-Path' => 'from@example.org', + ], + ]; + + // Verify we use line endings consistent with the PHP mail() function, which + // changed with PHP 8. See: + // - https://www.drupal.org/node/3270647 + // - https://bugs.php.net/bug.php?id=81158 + $line_end = "\r\n"; + + /** @var \Symfony\Component\Mailer\MailerInterface|\PHPUnit\Framework\MockObject\MockObject */ + $mailer = $this->getMockBuilder(MailerInterface::class)->getMock(); + $mailer->expects($this->once())->method('send') + ->with( + $this->logicalAnd( + $this->callback(fn (Email $email) => + $email->getHeaders()->get('mime-version')->getBodyAsString() === '1.0' + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->has('content-type') === FALSE + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->has('content-transfer-encoding') === FALSE + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->get('x-mailer')->getBodyAsString() === 'Drupal' + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->get('from')->getBodyAsString() === '"Foo, Bar, and Baz" <from@example.org>' + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->get('reply-to')->getBodyAsString() === 'from@example.org' + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->get('to')->getBodyAsString() === 'to@example.org' + ), + $this->callback(fn (Email $email) => + $email->getHeaders()->get('subject')->getBodyAsString() === "=?utf-8?Q?test?=$line_end =?utf-8?Q?subject?=" + ), + $this->callback(fn (Email $email) => + $email->getTextBody() === '' + ) + ) + ); + + /** @var \Psr\Log\LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + + $plugin = new SymfonyMailer($logger, $mailer); + $this->assertTrue($plugin->mail($message)); + } + +} -- GitLab