summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Scheller <igor.scheller@igorshp.de>2018-09-26 21:28:49 +0200
committerIgor Scheller <igor.scheller@igorshp.de>2018-09-26 21:31:18 +0200
commit6187eed3bb08f200050a3078bd762b5731dfbe78 (patch)
tree06e0b08294c66449ed5f9f046a3bfe2097c7b4ad
parentbc5764b33ffc8a92dfa1d788ba2a204dd991c3d9 (diff)
parentd36de2d26f5af76d5d4f34f8620694c6d0368983 (diff)
Merge remote-tracking branch 'MyIgel/mailing'
-rw-r--r--composer.json1
-rw-r--r--config/app.php13
-rw-r--r--config/config.default.php40
-rw-r--r--includes/helper/email_helper.php37
-rw-r--r--src/Mail/EngelsystemMailer.php35
-rw-r--r--src/Mail/Mailer.php79
-rw-r--r--src/Mail/MailerServiceProvider.php89
-rw-r--r--src/Mail/Transport/LogTransport.php44
-rw-r--r--src/Mail/Transport/Transport.php103
-rw-r--r--templates/emails/mail.twig6
-rw-r--r--tests/Unit/Mail/EngelsystemMailerTest.php40
-rw-r--r--tests/Unit/Mail/MailerServiceProviderTest.php177
-rw-r--r--tests/Unit/Mail/MailerTest.php77
-rw-r--r--tests/Unit/Mail/Transport/LogTransportTest.php60
-rw-r--r--tests/Unit/Mail/Transport/Stub/TransportImplementation.php35
-rw-r--r--tests/Unit/Mail/Transport/TransportTest.php83
16 files changed, 881 insertions, 38 deletions
diff --git a/composer.json b/composer.json
index 622fbdfa..0632bbc3 100644
--- a/composer.json
+++ b/composer.json
@@ -29,6 +29,7 @@
"psr/container": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.0",
+ "swiftmailer/swiftmailer": "^6.1",
"symfony/http-foundation": "^3.3",
"symfony/psr-http-message-bridge": "^1.0",
"twig/extensions": "^1.5",
diff --git a/config/app.php b/config/app.php
index e309abe4..efe745e8 100644
--- a/config/app.php
+++ b/config/app.php
@@ -5,9 +5,13 @@
return [
// Service providers
'providers' => [
+
+ // Application bootstrap
\Engelsystem\Logger\LoggerServiceProvider::class,
\Engelsystem\Exceptions\ExceptionsServiceProvider::class,
\Engelsystem\Config\ConfigServiceProvider::class,
+
+ // Request handling
\Engelsystem\Http\UrlGeneratorServiceProvider::class,
\Engelsystem\Renderer\RendererServiceProvider::class,
\Engelsystem\Database\DatabaseServiceProvider::class,
@@ -19,13 +23,22 @@ return [
\Engelsystem\Renderer\TwigServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
+
+ // Additional services
+ \Engelsystem\Mail\MailerServiceProvider::class,
],
// Application middleware
'middleware' => [
+
+ // Basic initialization
\Engelsystem\Middleware\SendResponseHandler::class,
\Engelsystem\Middleware\ExceptionHandler::class,
+
+ // Changes of request/response parameters
\Engelsystem\Middleware\SetLocale::class,
+
+ // The application code
\Engelsystem\Middleware\ErrorHandler::class,
\Engelsystem\Middleware\RouteDispatcher::class,
\Engelsystem\Middleware\RequestHandler::class,
diff --git a/config/config.default.php b/config/config.default.php
index dfccab0a..659a26b7 100644
--- a/config/config.default.php
+++ b/config/config.default.php
@@ -26,8 +26,24 @@ return [
// Contact email address, linked on every page
'contact_email' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
- // From address of all emails
- 'no_reply_email' => env('NO_REPLY_EMAIL', 'noreply@engelsystem.de'),
+ // Email config
+ 'email' => [
+ // Can be mail, smtp, sendmail or log
+ 'driver' => env('MAIL_DRIVER', 'smtp'),
+ 'from' => [
+ // From address of all emails
+ 'address' => env('MAIL_FROM_ADDRESS', 'noreply@engelsystem.de'),
+ 'name' => env('MAIL_FROM_NAME', 'Engelsystem')
+ ],
+
+ 'host' => env('MAIL_HOST', 'localhost'),
+ 'port' => env('MAIL_PORT', 587),
+ // Transport encryption like tls
+ 'encryption' => env('MAIL_ENCRYPTION', null),
+ 'username' => env('MAIL_USERNAME'),
+ 'password' => env('MAIL_PASSWORD'),
+ 'sendmail' => env('MAIL_SENDMAIL', '/usr/sbin/sendmail -bs'),
+ ],
// Default theme, 1=style1.css
'theme' => env('THEME', 1),
@@ -55,28 +71,27 @@ return [
// Only arrived angels can sign up for shifts
'signup_requires_arrival' => false,
- // Anzahl Stunden bis zum Austragen eigener Schichten
+ // Number of hours that an angel has to sign out own shifts
'last_unsubscribe' => 3,
- // Setzt den zu verwendenden Crypto-Algorithmus (entsprechend der Dokumentation von crypt()).
- // Falls ein Benutzerpasswort in einem anderen Format gespeichert ist,
- // wird es bei der ersten Benutzung des Klartext-Passworts in das neue Format
- // konvertiert.
+ // Define the algorithm to use for `crypt()` of passwords
+ // If the user uses an old algorithm the password will be converted to the new format
// MD5 '$1'
// Blowfish '$2y$13'
// SHA-256 '$5$rounds=5000'
// SHA-512 '$6$rounds=5000'
'crypt_alg' => '$6$rounds=5000',
+ // The minimum length for passwords
'min_password_length' => 8,
- // Wenn Engel beim Registrieren oder in ihrem Profil eine T-Shirt Größe angeben sollen, auf true setzen:
+ // Enables the T-Shirt configuration on signup and profile
'enable_tshirt_size' => true,
// Number of shifts to freeload until angel is locked for shift signup.
'max_freeloadable_shifts' => 2,
- // local timezone
+ // Local timezone
'timezone' => env('TIMEZONE', 'Europe/Berlin'),
// Multiply 'night shifts' and freeloaded shifts (start or end between 2 and 6 exclusive) by 2
@@ -99,10 +114,11 @@ return [
'en_US.UTF-8' => 'English',
],
- 'default_locale' => env('DEFAULT_LOCALE', 'en_US.UTF-8'),
+ // The default locale to use
+ 'default_locale' => env('DEFAULT_LOCALE', 'en_US.UTF-8'),
// Available T-Shirt sizes, set value to null if not available
- 'tshirt_sizes' => [
+ 'tshirt_sizes' => [
'S' => 'S',
'S-G' => 'S Girl',
'M' => 'M',
@@ -117,5 +133,5 @@ return [
],
// IP addresses of reverse proxies that are trusted, can be an array or a comma separated list
- 'trusted_proxies' => env('TRUSTED_PROXIES', ['127.0.0.0/8', '::ffff:127.0.0.0/8', '::1/128']),
+ 'trusted_proxies' => env('TRUSTED_PROXIES', ['127.0.0.0/8', '::ffff:127.0.0.0/8', '::1/128']),
];
diff --git a/includes/helper/email_helper.php b/includes/helper/email_helper.php
index a0631550..e9e5a323 100644
--- a/includes/helper/email_helper.php
+++ b/includes/helper/email_helper.php
@@ -1,5 +1,7 @@
<?php
+use Engelsystem\Mail\EngelsystemMailer;
+
/**
* @param array $recipient_user
* @param string $title
@@ -18,38 +20,21 @@ function engelsystem_email_to_user($recipient_user, $title, $message, $not_if_it
/** @var \Engelsystem\Helpers\Translator $translator */
$translator = app()->get('translator');
$locale = $translator->getLocale();
+ /** @var EngelsystemMailer $mailer */
+ $mailer = app('mailer');
$translator->setLocale($recipient_user['Sprache']);
- $message = sprintf(__('Hi %s,'), $recipient_user['Nick']) . "\n\n"
- . __('here is a message for you from the engelsystem:') . "\n\n"
- . $message . "\n\n"
- . __('This email is autogenerated and has not been signed. You got this email because you are registered in the engelsystem.');
- $translator->setLocale($locale);
-
- return engelsystem_email($recipient_user['email'], $title, $message);
-}
-
-/**
- * @param string $address
- * @param string $title
- * @param string $message
- * @return bool
- */
-function engelsystem_email($address, $title, $message)
-{
- $result = mail(
- $address,
+ $status = $mailer->sendView(
+ $recipient_user['email'],
$title,
- $message,
- sprintf(
- "Content-Type: text/plain; charset=UTF-8\r\nFrom: Engelsystem <%s>",
- config('no_reply_email')
- )
+ 'emails/mail',
+ ['user' => $recipient_user['Nick'], 'message' => $message]
);
+ $translator->setLocale($locale);
- if ($result === false) {
+ if (!$status) {
engelsystem_error('Unable to send email.');
}
- return true;
+ return (bool)$status;
}
diff --git a/src/Mail/EngelsystemMailer.php b/src/Mail/EngelsystemMailer.php
new file mode 100644
index 00000000..17047cc8
--- /dev/null
+++ b/src/Mail/EngelsystemMailer.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Engelsystem\Mail;
+
+use Engelsystem\Renderer\Renderer;
+use Swift_Mailer as SwiftMailer;
+
+class EngelsystemMailer extends Mailer
+{
+ /** @var Renderer|null */
+ protected $view;
+
+ public function __construct(SwiftMailer $mailer, Renderer $view = null)
+ {
+ parent::__construct($mailer);
+
+ $this->view = $view;
+ }
+
+ /**
+ * Send a template
+ *
+ * @param string $to
+ * @param string $subject
+ * @param string $template
+ * @param array $data
+ * @return int
+ */
+ public function sendView($to, $subject, $template, $data = []): int
+ {
+ $body = $this->view->render($template, $data);
+
+ return $this->send($to, $subject, $body);
+ }
+}
diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php
new file mode 100644
index 00000000..ed800986
--- /dev/null
+++ b/src/Mail/Mailer.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Engelsystem\Mail;
+
+use Engelsystem\Renderer\Renderer;
+use Swift_Mailer as SwiftMailer;
+use Swift_Message as SwiftMessage;
+
+class Mailer
+{
+ /** @var SwiftMailer */
+ protected $mailer;
+
+ /** @var Renderer|null */
+ protected $view;
+
+ /** @var string */
+ protected $fromAddress = '';
+
+ /** @var string */
+ protected $fromName = null;
+
+ public function __construct(SwiftMailer $mailer)
+ {
+ $this->mailer = $mailer;
+ }
+
+ /**
+ * Send the mail
+ *
+ * @param string|string[] $to
+ * @param string $subject
+ * @param string $body
+ * @return int
+ */
+ public function send($to, string $subject, string $body): int
+ {
+ /** @var SwiftMessage $message */
+ $message = $this->mailer->createMessage();
+ $message->setTo((array)$to)
+ ->setFrom($this->fromAddress, $this->fromName)
+ ->setSubject($subject)
+ ->setBody($body);
+
+ return $this->mailer->send($message);
+ }
+
+ /**
+ * @return string
+ */
+ public function getFromAddress(): string
+ {
+ return $this->fromAddress;
+ }
+
+ /**
+ * @param string $fromAddress
+ */
+ public function setFromAddress(string $fromAddress)
+ {
+ $this->fromAddress = $fromAddress;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFromName(): string
+ {
+ return $this->fromName;
+ }
+
+ /**
+ * @param string $fromName
+ */
+ public function setFromName(string $fromName)
+ {
+ $this->fromName = $fromName;
+ }
+}
diff --git a/src/Mail/MailerServiceProvider.php b/src/Mail/MailerServiceProvider.php
new file mode 100644
index 00000000..989fee8f
--- /dev/null
+++ b/src/Mail/MailerServiceProvider.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Engelsystem\Mail;
+
+use Engelsystem\Config\Config;
+use Engelsystem\Container\ServiceProvider;
+use Engelsystem\Mail\Transport\LogTransport;
+use InvalidArgumentException;
+use Swift_Mailer as SwiftMailer;
+use Swift_SendmailTransport as SendmailTransport;
+use Swift_SmtpTransport as SmtpTransport;
+use Swift_Transport as Transport;
+
+class MailerServiceProvider extends ServiceProvider
+{
+ public function register()
+ {
+ /** @var Config $config */
+ $config = $this->app->get('config');
+ $mailConfig = $config->get('email');
+
+ $transport = $this->getTransport($mailConfig['driver'], $mailConfig);
+ $this->app->instance(Transport::class, $transport);
+ $this->app->instance('mailer.transport', $transport);
+
+ /** @var SwiftMailer $swiftMailer */
+ $swiftMailer = $this->app->make(SwiftMailer::class);
+ $this->app->instance(SwiftMailer::class, $swiftMailer);
+ $this->app->instance('mailer.swift', $swiftMailer);
+
+ /** @var Mailer $mailer */
+ $mailer = $this->app->make(EngelsystemMailer::class);
+ $mailer->setFromAddress($mailConfig['from']['address']);
+ if (!empty($mailConfig['from']['name'])) {
+ $mailer->setFromName($mailConfig['from']['name']);
+ }
+
+ $this->app->instance(EngelsystemMailer::class, $mailer);
+ $this->app->instance(Mailer::class, $mailer);
+ $this->app->instance('mailer', $mailer);
+ }
+
+ /**
+ * @param string $transport
+ * @param array $config
+ * @return Transport
+ */
+ protected function getTransport($transport, $config)
+ {
+ switch ($transport) {
+ case 'log':
+ return $this->app->make(LogTransport::class);
+ case 'mail':
+ case 'sendmail':
+ return $this->app->make(SendmailTransport::class, ['command' => $config['sendmail']]);
+ case 'smtp':
+ return $this->getSmtpTransport($config);
+ }
+
+ throw new InvalidArgumentException(sprintf('Mail driver "%s" not found', $transport));
+ }
+
+ /**
+ * @param array $config
+ * @return SmtpTransport
+ */
+ protected function getSmtpTransport(array $config)
+ {
+ /** @var SmtpTransport $transport */
+ $transport = $this->app->make(SmtpTransport::class, [
+ 'host' => $config['host'],
+ 'port' => $config['port'],
+ 'encryption' => $config['encryption'],
+ // TODO: The security variable should be removed in the future
+ // https://github.com/swiftmailer/swiftmailer/commit/d3d6a98ab7dc155a04eb08273db7cd34606e7b5e#commitcomment-30462876
+ 'security' => $config['encryption'],
+ ]);
+
+ if ($config['username']) {
+ $transport->setUsername($config['username']);
+ }
+
+ if ($config['password']) {
+ $transport->setPassword($config['password']);
+ }
+
+ return $transport;
+ }
+}
diff --git a/src/Mail/Transport/LogTransport.php b/src/Mail/Transport/LogTransport.php
new file mode 100644
index 00000000..6e351302
--- /dev/null
+++ b/src/Mail/Transport/LogTransport.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Engelsystem\Mail\Transport;
+
+use Psr\Log\LoggerInterface;
+use Swift_Mime_SimpleMessage as SimpleMessage;
+
+class LogTransport extends Transport
+{
+ /** @var LoggerInterface */
+ protected $logger;
+
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Send the given Message.
+ *
+ * Recipient/sender data will be retrieved from the Message API.
+ * The return value is the number of recipients
+ *
+ * @param SimpleMessage $message
+ * @param string[] $failedRecipients An array of failures by-reference
+ *
+ * @return int
+ */
+ public function send(
+ SimpleMessage $message,
+ &$failedRecipients = null
+ ): int {
+ $this->logger->debug(
+ 'Mail: Send mail "{title}" to "{recipients}":' . PHP_EOL . '{content}',
+ [
+ 'title' => $message->getSubject(),
+ 'recipients' => $this->getTo($message),
+ 'content' => (string)$message->getHeaders() . PHP_EOL . PHP_EOL . $message->toString(),
+ ]
+ );
+
+ return count($this->allRecipients($message));
+ }
+}
diff --git a/src/Mail/Transport/Transport.php b/src/Mail/Transport/Transport.php
new file mode 100644
index 00000000..691faf60
--- /dev/null
+++ b/src/Mail/Transport/Transport.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Engelsystem\Mail\Transport;
+
+use Swift_Events_EventListener;
+use Swift_Mime_SimpleMessage as SimpleMessage;
+use Swift_Transport as SwiftTransport;
+
+abstract class Transport implements SwiftTransport
+{
+ /**
+ * Test if this Transport mechanism has started.
+ *
+ * @return bool
+ */
+ public function isStarted(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Start this Transport mechanism.
+ */
+ public function start() { }
+
+ /**
+ * Stop this Transport mechanism.
+ */
+ public function stop() { }
+
+ /**
+ * Check if this Transport mechanism is alive.
+ *
+ * If a Transport mechanism session is no longer functional, the method
+ * returns FALSE. It is the responsibility of the developer to handle this
+ * case and restart the Transport mechanism manually.
+ *
+ * @example
+ *
+ * if (!$transport->ping()) {
+ * $transport->stop();
+ * $transport->start();
+ * }
+ *
+ * The Transport mechanism will be started, if it is not already.
+ *
+ * It is undefined if the Transport mechanism attempts to restart as long as
+ * the return value reflects whether the mechanism is now functional.
+ *
+ * @return bool TRUE if the transport is alive
+ */
+ public function ping(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Register a plugin in the Transport.
+ *
+ * @param Swift_Events_EventListener $plugin
+ */
+ public function registerPlugin(Swift_Events_EventListener $plugin) { }
+
+ /**
+ * Returns a unified list of all recipients
+ *
+ * @param SimpleMessage $message
+ * @return array
+ */
+ protected function allRecipients(SimpleMessage $message): array
+ {
+ return array_merge(
+ (array)$message->getTo(),
+ (array)$message->getCc(),
+ (array)$message->getBcc()
+ );
+ }
+
+ /**
+ * Returns a concatenated list of mail recipients
+ *
+ * @param SimpleMessage $message
+ * @return string
+ */
+ protected function getTo(SimpleMessage $message): string
+ {
+ return $this->formatTo($this->allRecipients($message));
+ }
+
+ /**
+ * @param array $recipients
+ * @return string
+ */
+ protected function formatTo(array $recipients)
+ {
+ $list = [];
+ foreach ($recipients as $address => $name) {
+ $list[] = $name ? sprintf('%s <%s>', $name, $address) : $address;
+ }
+
+ return implode(',', $list);
+ }
+}
diff --git a/templates/emails/mail.twig b/templates/emails/mail.twig
new file mode 100644
index 00000000..f5ac5860
--- /dev/null
+++ b/templates/emails/mail.twig
@@ -0,0 +1,6 @@
+{{ __('Hi %s,', [user]) }}
+
+{{ __('here is a message for you from the engelsystem:') }}
+{{ message|raw }}
+
+{{ __('This email is autogenerated and has not been signed. You got this email because you are registered in the engelsystem.') }}
diff --git a/tests/Unit/Mail/EngelsystemMailerTest.php b/tests/Unit/Mail/EngelsystemMailerTest.php
new file mode 100644
index 00000000..aae6e267
--- /dev/null
+++ b/tests/Unit/Mail/EngelsystemMailerTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Mail;
+
+use Engelsystem\Mail\EngelsystemMailer;
+use Engelsystem\Renderer\Renderer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Swift_Mailer as SwiftMailer;
+
+class EngelsystemMailerTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Mail\EngelsystemMailer::__construct
+ * @covers \Engelsystem\Mail\EngelsystemMailer::sendView
+ */
+ public function testSendView()
+ {
+ /** @var Renderer|MockObject $view */
+ $view = $this->createMock(Renderer::class);
+ /** @var SwiftMailer|MockObject $swiftMailer */
+ $swiftMailer = $this->createMock(SwiftMailer::class);
+ /** @var EngelsystemMailer|MockObject $mailer */
+ $mailer = $this->getMockBuilder(EngelsystemMailer::class)
+ ->setConstructorArgs(['mailer' => $swiftMailer, 'view' => $view])
+ ->setMethods(['send'])
+ ->getMock();
+ $mailer->expects($this->once())
+ ->method('send')
+ ->with('foo@bar.baz', 'Lorem dolor', 'Rendered Stuff!')
+ ->willReturn(1);
+ $view->expects($this->once())
+ ->method('render')
+ ->with('test/template.tpl', ['dev' => true])
+ ->willReturn('Rendered Stuff!');
+
+ $return = $mailer->sendView('foo@bar.baz', 'Lorem dolor', 'test/template.tpl', ['dev' => true]);
+ $this->equalTo(1, $return);
+ }
+}
diff --git a/tests/Unit/Mail/MailerServiceProviderTest.php b/tests/Unit/Mail/MailerServiceProviderTest.php
new file mode 100644
index 00000000..0c841e9e
--- /dev/null
+++ b/tests/Unit/Mail/MailerServiceProviderTest.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Middleware;
+
+use Engelsystem\Application;
+use Engelsystem\Config\Config;
+use Engelsystem\Mail\EngelsystemMailer;
+use Engelsystem\Mail\Mailer;
+use Engelsystem\Mail\MailerServiceProvider;
+use Engelsystem\Mail\Transport\LogTransport;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use Swift_Mailer as SwiftMailer;
+use Swift_SendmailTransport as SendmailTransport;
+use Swift_SmtpTransport as SmtpTransport;
+use Swift_Transport as Transport;
+
+class MailerServiceProviderTest extends ServiceProviderTest
+{
+ /** @var array */
+ protected $defaultConfig = [
+ 'email' => [
+ 'driver' => 'mail',
+ 'from' => [
+ 'name' => 'Engelsystem',
+ 'address' => 'foo@bar.batz',
+ ],
+ 'sendmail' => '/opt/bin/sendmail -bs',
+ ],
+ ];
+
+ /** @var array */
+ protected $smtpConfig = [
+ 'email' => [
+ 'driver' => 'smtp',
+ 'host' => 'mail.foo.bar',
+ 'port' => 587,
+ 'encryption' => 'tls',
+ 'username' => 'foobar',
+ 'password' => 'LoremIpsum123',
+ ],
+ ];
+
+ /**
+ * @covers \Engelsystem\Mail\MailerServiceProvider::register
+ */
+ public function testRegister()
+ {
+ $app = $this->getApplication();
+
+ $serviceProvider = new MailerServiceProvider($app);
+ $serviceProvider->register();
+
+ $this->assertExistsInContainer(['mailer.transport', Transport::class], $app);
+ $this->assertExistsInContainer(['mailer.swift', SwiftMailer::class], $app);
+ $this->assertExistsInContainer(['mailer', EngelsystemMailer::class, Mailer::class], $app);
+
+ /** @var EngelsystemMailer $mailer */
+ $mailer = $app->get('mailer');
+ $this->assertEquals('Engelsystem', $mailer->getFromName());
+ $this->assertEquals('foo@bar.batz', $mailer->getFromAddress());
+
+ /** @var SendmailTransport $transport */
+ $transport = $app->get('mailer.transport');
+ $this->assertEquals($this->defaultConfig['email']['sendmail'], $transport->getCommand());
+ }
+
+ /**
+ * @return array
+ */
+ public function provideTransports()
+ {
+ return [
+ [LogTransport::class, ['email' => ['driver' => 'log']]],
+ [SendmailTransport::class, ['email' => ['driver' => 'mail']]],
+ [SendmailTransport::class, ['email' => ['driver' => 'sendmail']]],
+ [
+ SmtpTransport::class,
+ $this->smtpConfig
+ ],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\MailerServiceProvider::getTransport
+ * @param string $class
+ * @param array $emailConfig
+ * @dataProvider provideTransports
+ */
+ public function testGetTransport($class, $emailConfig = [])
+ {
+ $app = $this->getApplication($emailConfig);
+
+ $serviceProvider = new MailerServiceProvider($app);
+ $serviceProvider->register();
+
+ $transport = $app->get('mailer.transport');
+ $this->assertInstanceOf($class, $transport);
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\MailerServiceProvider::getTransport
+ */
+ public function testGetTransportNotFound()
+ {
+ $app = $this->getApplication(['email' => ['driver' => 'foo-bar-batz']]);
+ $this->expectException(InvalidArgumentException::class);
+
+ $serviceProvider = new MailerServiceProvider($app);
+ $serviceProvider->register();
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\MailerServiceProvider::getSmtpTransport
+ */
+ public function testGetSmtpTransport()
+ {
+ $app = $this->getApplication($this->smtpConfig);
+
+ $serviceProvider = new MailerServiceProvider($app);
+ $serviceProvider->register();
+
+ /** @var SmtpTransport $transport */
+ $transport = $app->get('mailer.transport');
+
+ $this->assertEquals($this->smtpConfig['email']['host'], $transport->getHost());
+ $this->assertEquals($this->smtpConfig['email']['port'], $transport->getPort());
+ $this->assertEquals($this->smtpConfig['email']['encryption'], $transport->getEncryption());
+ $this->assertEquals($this->smtpConfig['email']['username'], $transport->getUsername());
+ $this->assertEquals($this->smtpConfig['email']['password'], $transport->getPassword());
+ }
+
+ /**
+ * @param array $configuration
+ * @return Application
+ */
+ protected function getApplication($configuration = []): Application
+ {
+ $app = new Application();
+
+ $configuration = new Config(array_replace_recursive($this->defaultConfig, $configuration));
+ $app->instance('config', $configuration);
+
+ $logger = $this->getMockForAbstractClass(LoggerInterface::class);
+ $app->instance(LoggerInterface::class, $logger);
+
+ return $app;
+ }
+
+ /**
+ * @param string[] $abstracts
+ * @param Application $container
+ */
+ protected function assertExistsInContainer($abstracts, $container)
+ {
+ $first = array_shift($abstracts);
+ $this->assertContainerHas($first, $container);
+
+ foreach ($abstracts as $abstract) {
+ $this->assertContainerHas($abstract, $container);
+ $this->assertEquals($container->get($first), $container->get($abstract));
+ }
+ }
+
+ /**
+ * @param string $abstract
+ * @param Application $container
+ */
+ protected function assertContainerHas($abstract, $container)
+ {
+ $this->assertTrue(
+ $container->has($abstract) || $container->hasMethodBinding($abstract),
+ sprintf('Container does not contain abstract %s', $abstract)
+ );
+ }
+}
diff --git a/tests/Unit/Mail/MailerTest.php b/tests/Unit/Mail/MailerTest.php
new file mode 100644
index 00000000..24c6f69a
--- /dev/null
+++ b/tests/Unit/Mail/MailerTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Mail;
+
+use Engelsystem\Mail\Mailer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Swift_Mailer as SwiftMailer;
+use Swift_Message as SwiftMessage;
+
+class MailerTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Mail\Mailer::__construct
+ * @covers \Engelsystem\Mail\Mailer::getFromAddress
+ * @covers \Engelsystem\Mail\Mailer::setFromAddress
+ * @covers \Engelsystem\Mail\Mailer::getFromName
+ * @covers \Engelsystem\Mail\Mailer::setFromName
+ */
+ public function testInitAndSettersAndGetters()
+ {
+ /** @var SwiftMailer|MockObject $swiftMailer */
+ $swiftMailer = $this->createMock(SwiftMailer::class);
+
+ $mailer = new Mailer($swiftMailer);
+
+ $mailer->setFromName('From Name');
+ $this->assertEquals('From Name', $mailer->getFromName());
+
+ $mailer->setFromAddress('from@foo.bar');
+ $this->assertEquals('from@foo.bar', $mailer->getFromAddress());
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\Mailer::send
+ */
+ public function testSend()
+ {
+ /** @var SwiftMessage|MockObject $message */
+ $message = $this->createMock(SwiftMessage::class);
+ /** @var SwiftMailer|MockObject $swiftMailer */
+ $swiftMailer = $this->createMock(SwiftMailer::class);
+ $swiftMailer->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($message);
+ $swiftMailer->expects($this->once())
+ ->method('send')
+ ->willReturn(1);
+
+ $message->expects($this->once())
+ ->method('setTo')
+ ->with(['to@xam.pel'])
+ ->willReturn($message);
+
+ $message->expects($this->once())
+ ->method('setFrom')
+ ->with('foo@bar.baz', 'Lorem Ipsum')
+ ->willReturn($message);
+
+ $message->expects($this->once())
+ ->method('setSubject')
+ ->with('Foo Bar')
+ ->willReturn($message);
+
+ $message->expects($this->once())
+ ->method('setBody')
+ ->with('Lorem Ipsum!')
+ ->willReturn($message);
+
+ $mailer = new Mailer($swiftMailer);
+ $mailer->setFromAddress('foo@bar.baz');
+ $mailer->setFromName('Lorem Ipsum');
+
+ $return = $mailer->send('to@xam.pel', 'Foo Bar', 'Lorem Ipsum!');
+ $this->equalTo(1, $return);
+ }
+}
diff --git a/tests/Unit/Mail/Transport/LogTransportTest.php b/tests/Unit/Mail/Transport/LogTransportTest.php
new file mode 100644
index 00000000..5eb3a667
--- /dev/null
+++ b/tests/Unit/Mail/Transport/LogTransportTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Mail\Transport;
+
+use Engelsystem\Mail\Transport\LogTransport;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Swift_Mime_SimpleMessage as SimpleMessage;
+
+class LogTransportTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Mail\Transport\LogTransport::__construct
+ * @covers \Engelsystem\Mail\Transport\LogTransport::send
+ */
+ public function testSend()
+ {
+ /** @var LoggerInterface|MockObject $logger */
+ $logger = $this->getMockForAbstractClass(LoggerInterface::class);
+ /** @var SimpleMessage|MockObject $message */
+ $message = $this->createMock(SimpleMessage::class);
+
+ $message->expects($this->once())
+ ->method('getSubject')
+ ->willReturn('Some subject');
+ $message->expects($this->once())
+ ->method('getHeaders')
+ ->willReturn('Head: er');
+ $message->expects($this->once())
+ ->method('toString')
+ ->willReturn('Message body');
+
+ $logger->expects($this->once())
+ ->method('debug')
+ ->willReturnCallback(function ($message, $context = []) {
+ foreach (array_keys($context) as $key) {
+ $this->assertContains(sprintf('{%s}', $key), $message);
+ }
+
+ $this->assertEquals('Some subject', $context['title']);
+ $this->assertEquals('foo@bar.batz,Lorem Ipsum <lor@em.ips>', $context['recipients']);
+ $this->assertContains('Head: er', $context['content']);
+ $this->assertContains('Message body', $context['content']);
+ });
+
+ /** @var LogTransport|MockObject $transport */
+ $transport = $this->getMockBuilder(LogTransport::class)
+ ->setConstructorArgs(['logger' => $logger])
+ ->setMethods(['allRecipients'])
+ ->getMock();
+ $transport->expects($this->exactly(2))
+ ->method('allRecipients')
+ ->with($message)
+ ->willReturn(['foo@bar.batz' => null, 'lor@em.ips' => 'Lorem Ipsum']);
+
+ $return = $transport->send($message);
+ $this->equalTo(2, $return);
+ }
+}
diff --git a/tests/Unit/Mail/Transport/Stub/TransportImplementation.php b/tests/Unit/Mail/Transport/Stub/TransportImplementation.php
new file mode 100644
index 00000000..e3667c6e
--- /dev/null
+++ b/tests/Unit/Mail/Transport/Stub/TransportImplementation.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Mail\Transport\Stub;
+
+use Engelsystem\Mail\Transport\Transport;
+use Swift_Mime_SimpleMessage as SimpleMessage;
+
+class TransportImplementation extends Transport
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function send(SimpleMessage $message, &$failedRecipients = null)
+ {
+ return 0;
+ }
+
+ /**
+ * @param SimpleMessage $message
+ * @return array
+ */
+ public function getAllRecipients(SimpleMessage $message)
+ {
+ return $this->allRecipients($message);
+ }
+
+ /**
+ * @param SimpleMessage $message
+ * @return string
+ */
+ public function getGetTo(SimpleMessage $message)
+ {
+ return $this->getTo($message);
+ }
+}
diff --git a/tests/Unit/Mail/Transport/TransportTest.php b/tests/Unit/Mail/Transport/TransportTest.php
new file mode 100644
index 00000000..60f2079d
--- /dev/null
+++ b/tests/Unit/Mail/Transport/TransportTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Mail\Transport;
+
+use Engelsystem\Test\Unit\Mail\Transport\Stub\TransportImplementation;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Swift_Mime_SimpleMessage as SimpleMessage;
+
+class TransportTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Mail\Transport\Transport::isStarted
+ * @covers \Engelsystem\Mail\Transport\Transport::ping
+ */
+ public function testMethods()
+ {
+ $transport = new TransportImplementation();
+
+ $this->assertTrue($transport->isStarted());
+ $this->assertTrue($transport->ping());
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\Transport\Transport::allRecipients
+ */
+ public function testAllRecipients()
+ {
+ /** @var SimpleMessage|MockObject $message */
+ $message = $this->createMock(SimpleMessage::class);
+ $transport = new TransportImplementation();
+ $message->expects($this->once())
+ ->method('getTo')
+ ->willReturn([
+ 'foo@bar.batz' => 'Foo Bar',
+ 'lorem@ipsum.dolor' => null,
+ ]);
+ $message->expects($this->once())
+ ->method('getCc')
+ ->willReturn([
+ 'to@bar.batz' => null,
+ ]);
+ $message->expects($this->once())
+ ->method('getBcc')
+ ->willReturn([
+ 'secret@bar.batz' => 'I\'m secret!',
+ ]);
+
+ $this->assertEquals(
+ [
+ 'foo@bar.batz' => 'Foo Bar',
+ 'lorem@ipsum.dolor' => null,
+ 'to@bar.batz' => null,
+ 'secret@bar.batz' => 'I\'m secret!',
+ ],
+ $transport->getAllRecipients($message)
+ );
+ }
+
+ /**
+ * @covers \Engelsystem\Mail\Transport\Transport::getTo
+ * @covers \Engelsystem\Mail\Transport\Transport::formatTo
+ */
+ public function testGetTo()
+ {
+ /** @var SimpleMessage|MockObject $message */
+ $message = $this->createMock(SimpleMessage::class);
+ /** @var TransportImplementation|MockObject $transport */
+ $transport = $this->getMockBuilder(TransportImplementation::class)
+ ->setMethods(['allRecipients'])
+ ->getMock();
+ $transport->expects($this->once())
+ ->method('allRecipients')
+ ->with($message)
+ ->willReturn([
+ 'foo@bar.batz' => null,
+ 'lorem@ipsum.dolor' => 'Developer',
+ ]);
+
+ $return = $transport->getGetTo($message);
+ $this->assertEquals('foo@bar.batz,Developer <lorem@ipsum.dolor>', $return);
+ }
+}