From 82458e569b09e3ea800582a38c5199cea87996b5 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 9 Feb 2022 21:46:06 +0100 Subject: [PATCH 01/60] Remove symfony/console --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7430806..583fb0d 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,6 @@ }, "require-dev": { "mockery/mockery": "^1.2", - "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0 || ^5.0", - "symfony/console": "^4.4|^5.0" + "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0 || ^5.0" } } From 80a16218b305124ab0286a63769e47552c902efa Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 9 Feb 2022 21:47:19 +0100 Subject: [PATCH 02/60] Test package with Laravel 9 --- .github/workflows/run-tests.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9bbc248..347a5d2 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,9 +12,11 @@ jobs: strategy: matrix: php: [8.1, 8.0, 7.4, 7.3, 7.2] - laravel: [8.*, 7.*, 6.*, 5.8.*, 5.7.*, 5.6.*] + laravel: [9.*, 8.*, 7.*, 6.*, 5.8.*, 5.7.*, 5.6.*] os: [ubuntu-latest] include: + - laravel: 9.* + testbench: 7.* - laravel: 8.* testbench: 6.* - laravel: 7.* @@ -28,6 +30,12 @@ jobs: - laravel: 5.6.* testbench: 3.6.* exclude: + - laravel: 9.* + php: 7.2 + - laravel: 9.* + php: 7.3 + - laravel: 9.* + php: 7.4 - laravel: 8.* php: 7.2 - laravel: 5.7.* From 55c0aaf83ed81eadf7348f8708dfdbaa9012c315 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 21:29:47 +0100 Subject: [PATCH 03/60] Make the package work with Laravel 9 --- src/MessageSent.php | 16 +++++++ src/Sender.php | 20 +++++++-- src/SentMessage.php | 70 ++++++++++++++++++++++++++++++ tests/SendEmailsCommandTest.php | 2 +- tests/SenderTest.php | 67 +++++++++++++++------------- tests/TestingMailEventListener.php | 5 ++- 6 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 src/MessageSent.php create mode 100644 src/SentMessage.php diff --git a/src/MessageSent.php b/src/MessageSent.php new file mode 100644 index 0000000..b90b5bc --- /dev/null +++ b/src/MessageSent.php @@ -0,0 +1,16 @@ +message = $message; + } +} \ No newline at end of file diff --git a/src/Sender.php b/src/Sender.php index 567819f..89e6788 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -2,6 +2,7 @@ namespace Stackkit\LaravelDatabaseEmails; +use Exception; use Illuminate\Mail\Message; use Illuminate\Support\Facades\Mail; @@ -20,10 +21,16 @@ public function send(Email $email) $email->markAsSending(); - Mail::send([], [], function (Message $message) use ($email) { + $sentMessage = Mail::send([], [], function (Message $message) use ($email) { $this->buildMessage($message, $email); }); + // This is used so we can assert things on the sent message in Laravel 9+ since we cannot use + // the Swift Mailer plugin anymore. So this is purely used for in the PHPUnit tests. + if (version_compare(app()->version(), '9.0.0', '>=') && !is_null($sentMessage)) { + event(new MessageSent($sentMessage)); + } + $email->markAsSent(); } @@ -39,8 +46,15 @@ private function buildMessage(Message $message, Email $email) ->cc($email->hasCc() ? $email->getCc() : []) ->bcc($email->hasBcc() ? $email->getBcc() : []) ->subject($email->getSubject()) - ->from($email->getFromAddress(), $email->getFromName()) - ->setBody($email->getBody(), 'text/html'); + ->from($email->getFromAddress(), $email->getFromName()); + + if (version_compare(app()->version(), '9.0.0', '>=')) { + // Symfony Mailer + $message->html($email->getBody()); + } else { + // SwiftMail + $message->setBody($email->getBody(), 'text/html'); + } $attachmentMap = [ 'attachment' => 'attach', diff --git a/src/SentMessage.php b/src/SentMessage.php new file mode 100644 index 0000000..ca2b069 --- /dev/null +++ b/src/SentMessage.php @@ -0,0 +1,70 @@ +getFrom() as $address) { + $sentMessage->from[$address->getAddress()] = $address->getName(); + } + + foreach ($email->getTo() as $address) { + $sentMessage->to[$address->getAddress()] = $address->getName(); + } + + foreach ($email->getCc() as $address) { + $sentMessage->cc[$address->getAddress()] = $address->getName(); + } + + foreach ($email->getBcc() as $address) { + $sentMessage->bcc[$address->getAddress()] = $address->getName(); + } + + $sentMessage->subject = $email->getSubject(); + $sentMessage->body = $email->getHtmlBody(); + $sentMessage->attachments = array_map(function (DataPart $dataPart) { + return [ + 'body' => $dataPart->getBody(), + 'disposition' => $dataPart->asDebugString(), + ]; + }, $email->getAttachments()); + + return $sentMessage; + } + + public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message) + { + $sentMessage = new self(); + + $sentMessage->from = $message->getFrom(); + $sentMessage->to = $message->getTo(); + $sentMessage->cc = $message->getCc(); + $sentMessage->bcc = $message->getBcc(); + $sentMessage->subject = $message->getSubject(); + $sentMessage->body = $message->getBody(); + $sentMessage->attachments = array_map(function(Swift_Mime_SimpleMimeEntity $entity) { + return [ + 'body' => $entity->getBody(), + 'disposition' => $entity->getContentType() . ' ' . $entity->getHeaders()->get('content-disposition'), + ]; + }, $message->getChildren()); + + return $sentMessage; + } +} \ No newline at end of file diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 5aacca4..6c58ea5 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -68,7 +68,7 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database $this->artisan('email:send'); $this->assertTrue($email->fresh()->hasFailed()); - $this->assertStringContains('Swift_RfcComplianceException', $email->fresh()->getError()); + $this->assertStringContains('RfcComplianceException', $email->fresh()->getError()); } /** @test */ diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 9cc2bfd..697c745 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -2,9 +2,13 @@ namespace Tests; +use Illuminate\Support\Facades\Event; +use Stackkit\LaravelDatabaseEmails\MessageSent; +use Stackkit\LaravelDatabaseEmails\SentMessage; use Swift_Events_SendEvent; use Illuminate\Support\Facades\Mail; use Stackkit\LaravelDatabaseEmails\Email; +use Symfony\Component\Mime\Part\DataPart; class SenderTest extends TestCase { @@ -15,7 +19,15 @@ public function setUp(): void { parent::setUp(); - Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this)); + if (version_compare(app()->version(), '9.0.0', '>=')) { + Event::listen(MessageSent::class, function (MessageSent $event) { + $this->sent[] = SentMessage::createFromSymfonyMailer( + $event->message->getSymfonySentMessage()->getOriginalMessage() + ); + }); + } else { + Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this)); + } } /** @test */ @@ -38,7 +50,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->artisan('email:send'); - $from = reset($this->sent)->getMessage()->getFrom(); + $from = reset($this->sent)->from; $this->assertEquals('testfromaddress@gmail.com', key($from)); $this->assertEquals('From CI test', $from[key($from)]); @@ -48,7 +60,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); $this->artisan('email:send'); - $from = reset($this->sent)->getMessage()->getFrom(); + $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); $this->assertEquals('Marick', $from[key($from)]); @@ -56,7 +68,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->sent = []; $this->composeEmail()->from('marick@dolphiq.nl')->send(); $this->artisan('email:send'); - $from = reset($this->sent)->getMessage()->getFrom(); + $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); $this->assertEquals(config('mail.from.name'), $from[key($from)]); @@ -64,7 +76,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->sent = []; $this->composeEmail()->from(null, 'Marick')->send(); $this->artisan('email:send'); - $from = reset($this->sent)->getMessage()->getFrom(); + $from = reset($this->sent)->from; $this->assertEquals(config('mail.from.address'), key($from)); $this->assertEquals('Marick', $from[key($from)]); } @@ -74,14 +86,14 @@ public function it_sends_emails_to_the_correct_recipients() { $this->sendEmail(['recipient' => 'john@doe.com']); $this->artisan('email:send'); - $to = reset($this->sent)->getMessage()->getTo(); + $to = reset($this->sent)->to; $this->assertCount(1, $to); $this->assertArrayHasKey('john@doe.com', $to); $this->sent = []; $this->sendEmail(['recipient' => ['john@doe.com', 'john+2@doe.com']]); $this->artisan('email:send'); - $to = reset($this->sent)->getMessage()->getTo(); + $to = reset($this->sent)->to; $this->assertCount(2, $to); $this->assertArrayHasKey('john@doe.com', $to); $this->assertArrayHasKey('john+2@doe.com', $to); @@ -92,14 +104,14 @@ public function it_adds_the_cc_addresses() { $this->sendEmail(['cc' => 'cc@test.com']); $this->artisan('email:send'); - $cc = reset($this->sent)->getMessage()->getCc(); + $cc = reset($this->sent)->cc; $this->assertCount(1, $cc); $this->assertArrayHasKey('cc@test.com', $cc); $this->sent = []; $this->sendEmail(['cc' => ['cc@test.com', 'cc+2@test.com']]); $this->artisan('email:send'); - $cc = reset($this->sent)->getMessage()->getCc(); + $cc = reset($this->sent)->cc; $this->assertCount(2, $cc); $this->assertArrayHasKey('cc@test.com', $cc); $this->assertArrayHasKey('cc+2@test.com', $cc); @@ -110,14 +122,14 @@ public function it_adds_the_bcc_addresses() { $this->sendEmail(['bcc' => 'bcc@test.com']); $this->artisan('email:send'); - $bcc = reset($this->sent)->getMessage()->getBcc(); + $bcc = reset($this->sent)->bcc; $this->assertCount(1, $bcc); $this->assertArrayHasKey('bcc@test.com', $bcc); $this->sent = []; $this->sendEmail(['bcc' => ['bcc@test.com', 'bcc+2@test.com']]); $this->artisan('email:send'); - $bcc = reset($this->sent)->getMessage()->getBcc(); + $bcc = reset($this->sent)->bcc; $this->assertCount(2, $bcc); $this->assertArrayHasKey('bcc@test.com', $bcc); $this->assertArrayHasKey('bcc+2@test.com', $bcc); @@ -130,7 +142,7 @@ public function the_email_has_the_correct_subject() $this->artisan('email:send'); - $subject = reset($this->sent)->getMessage()->getSubject(); + $subject = reset($this->sent)->subject; $this->assertEquals('Hello World', $subject); } @@ -140,13 +152,13 @@ public function the_email_has_the_correct_body() { $this->sendEmail(['variables' => ['name' => 'John Doe']]); $this->artisan('email:send'); - $body = reset($this->sent)->getMessage()->getBody(); - $this->assertEquals(view('tests::dummy', ['name' => 'John Doe']), $body); + $body = reset($this->sent)->body; + $this->assertEquals((string) view('tests::dummy', ['name' => 'John Doe']), $body); $this->sent = []; $this->sendEmail(['variables' => []]); $this->artisan('email:send'); - $body = reset($this->sent)->getMessage()->getBody(); + $body = reset($this->sent)->body; $this->assertEquals(view('tests::dummy'), $body); } @@ -158,12 +170,9 @@ public function attachments_are_added_to_the_email() ->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); - $attachment = reset($attachments); + $attachments = reset($this->sent)->attachments; $this->assertCount(1, $attachments); - $this->assertEquals('attachment; filename=pdf-sample.pdf', $attachment->getHeaders()->get('content-disposition')->getFieldBody()); - $this->assertEquals('application/pdf', $attachment->getContentType()); } /** @test */ @@ -172,19 +181,19 @@ public function attachments_are_not_added_if_the_data_is_not_valid() $this->sent = []; $this->composeEmail()->attach(null)->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); $this->sent = []; $this->composeEmail()->attach(false)->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); $this->sent = []; $this->composeEmail()->attach('')->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); } @@ -200,13 +209,13 @@ public function raw_attachments_are_added_to_the_email() ->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $attachment = reset($attachments); $this->assertCount(1, $attachments); - $this->assertEquals('attachment; filename=hello-ci.pdf', $attachment->getHeaders()->get('content-disposition')->getFieldBody()); - $this->assertEquals('application/pdf', $attachment->getContentType()); - $this->assertTrue(md5($attachment->getBody()) == md5($rawData)); + $this->assertStringContainsString('hello-ci.pdf', $attachment['disposition']); + $this->assertStringContainsString('application/pdf', $attachment['disposition']); + $this->assertTrue(md5($attachment['body']) == md5($rawData)); } /** @test */ @@ -246,19 +255,19 @@ public function raw_attachments_are_not_added_if_the_data_is_not_valid() $this->sent = []; $this->composeEmail()->attachData(null, 'test.png')->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); $this->sent = []; $this->composeEmail()->attachData(false, 'test.png')->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); $this->sent = []; $this->composeEmail()->attachData('', 'test.png')->send(); $this->artisan('email:send'); - $attachments = reset($this->sent)->getMessage()->getChildren(); + $attachments = reset($this->sent)->attachments; $this->assertCount(0, $attachments); } } diff --git a/tests/TestingMailEventListener.php b/tests/TestingMailEventListener.php index b143350..4fa32f1 100644 --- a/tests/TestingMailEventListener.php +++ b/tests/TestingMailEventListener.php @@ -2,6 +2,7 @@ namespace Tests; +use Stackkit\LaravelDatabaseEmails\SentMessage; use Swift_Events_EventListener; class TestingMailEventListener implements Swift_Events_EventListener @@ -14,8 +15,8 @@ public function __construct(TestCase $test) $this->test = $test; } - public function beforeSendPerformed($event) + public function beforeSendPerformed(\Swift_Events_Event $event) { - $this->test->sent[] = $event; + $this->test->sent[] = SentMessage::createFromSwiftMailer($event->getMessage()); } } From 262d07b9e44dcd5f89dda18db3606a82e056e0ff Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 21:33:06 +0100 Subject: [PATCH 04/60] Add newlines at end of file --- src/MessageSent.php | 2 +- src/SentMessage.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MessageSent.php b/src/MessageSent.php index b90b5bc..527407e 100644 --- a/src/MessageSent.php +++ b/src/MessageSent.php @@ -13,4 +13,4 @@ public function __construct($message) { $this->message = $message; } -} \ No newline at end of file +} diff --git a/src/SentMessage.php b/src/SentMessage.php index ca2b069..1abde4b 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -67,4 +67,4 @@ public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message) return $sentMessage; } -} \ No newline at end of file +} From 8ec84bf22eb3656cf610a0caace286b44143ed7e Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 21:39:46 +0100 Subject: [PATCH 05/60] Cleanup unnecessary code --- src/MailableReader.php | 1 - src/Sender.php | 1 - tests/SendEmailsCommandTest.php | 2 +- tests/TestCase.php | 30 ------------------------------ 4 files changed, 1 insertion(+), 33 deletions(-) diff --git a/src/MailableReader.php b/src/MailableReader.php index 38c4872..d443351 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -3,7 +3,6 @@ namespace Stackkit\LaravelDatabaseEmails; use Exception; -use function call_user_func_array; use Illuminate\Container\Container; class MailableReader diff --git a/src/Sender.php b/src/Sender.php index 89e6788..614a049 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -2,7 +2,6 @@ namespace Stackkit\LaravelDatabaseEmails; -use Exception; use Illuminate\Mail\Message; use Illuminate\Support\Facades\Mail; diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 6c58ea5..ccd38d7 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -68,7 +68,7 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database $this->artisan('email:send'); $this->assertTrue($email->fresh()->hasFailed()); - $this->assertStringContains('RfcComplianceException', $email->fresh()->getError()); + $this->assertStringContainsString('RfcComplianceException', $email->fresh()->getError()); } /** @test */ diff --git a/tests/TestCase.php b/tests/TestCase.php index a2ddf40..6223864 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,6 @@ namespace Tests; -use Eloquent; use Stackkit\LaravelDatabaseEmails\Email; class TestCase extends \Orchestra\Testbench\TestCase @@ -32,26 +31,6 @@ function () { Email::truncate(); } - /** - * Get a database connection instance. - * - * @return \Illuminate\Database\Connection - */ - protected function connection() - { - return Eloquent::getConnectionResolver()->connection(); - } - - /** - * Get a schema builder instance. - * - * @return \Illuminate\Database\Schema\Builder - */ - protected function schema() - { - return $this->connection()->getSchemaBuilder(); - } - /** * Get package providers. At a minimum this is the package being tested, but also * would include packages upon which our package depends, e.g. Cartalyst/Sentry @@ -137,13 +116,4 @@ public function queueEmail($connection = null, $queue = null, $delay = null, $ov { return $this->createEmail($overwrite)->queue($connection, $queue, $delay); } - - public function assertStringContains($needle, $haystack) - { - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString($needle, $haystack); - } else { - $this->assertContains($needle, $haystack); - } - } } From 2886e9a8d995a1e5aecd464ca39b118ba07261e9 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 22:11:56 +0100 Subject: [PATCH 06/60] Add types --- src/Config.php | 14 +-- src/Email.php | 105 ++++++++++--------- src/EmailComposer.php | 80 +++++++------- src/Encrypter.php | 16 +-- src/HasEncryptedAttributes.php | 2 + src/LaravelDatabaseEmailsServiceProvider.php | 6 +- src/MailableReader.php | 20 ++-- src/MessageSent.php | 2 + src/Preparer.php | 30 +++--- src/SendEmailJob.php | 3 +- src/SendEmailsCommand.php | 16 +-- src/Sender.php | 8 +- src/SentMessage.php | 6 +- src/Store.php | 4 +- src/Validator.php | 20 ++-- tests/SenderTest.php | 45 -------- tests/ValidatorTest.php | 2 +- 17 files changed, 173 insertions(+), 206 deletions(-) diff --git a/src/Config.php b/src/Config.php index ab5a16d..28640c4 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1,5 +1,7 @@ id; } @@ -71,7 +73,7 @@ public function getId() * * @return string|null */ - public function getLabel() + public function getLabel(): ?string { return $this->label; } @@ -79,7 +81,7 @@ public function getLabel() /** * Get the e-mail recipient. * - * @return string + * @return string|array */ public function getRecipient() { @@ -89,7 +91,7 @@ public function getRecipient() /** * Get the e-mail recipient. * - * @return string + * @return string|array */ public function getRecipientAttribute() { @@ -99,9 +101,9 @@ public function getRecipientAttribute() /** * Get the e-mail from. * - * @return string + * @return array|null */ - public function getFrom() + public function getFrom(): ?array { return $this->from; } @@ -109,9 +111,9 @@ public function getFrom() /** * Get the e-mail from. * - * @return string + * @return array|null */ - public function getFromAttribute() + public function getFromAttribute(): ?array { return $this->from; } @@ -121,7 +123,7 @@ public function getFromAttribute() * * @return string|null */ - public function getFromAddress() + public function getFromAddress(): ?string { return $this->from['address'] ?? config('mail.from.address'); } @@ -131,7 +133,7 @@ public function getFromAddress() * * @return string|null */ - public function getFromName() + public function getFromName(): ?string { return $this->from['name'] ?? config('mail.from.name'); } @@ -141,7 +143,7 @@ public function getFromName() * * @return string */ - public function getRecipientsAsString() + public function getRecipientsAsString(): string { $glue = ', '; @@ -161,7 +163,7 @@ public function getCc() /** * Get the e-mail CC addresses. * - * @return array + * @return array|string */ public function getCcAttribute() { @@ -181,7 +183,7 @@ public function getBcc() /** * Get the e-mail BCC addresses. * - * @return array + * @return array|string */ public function getBccAttribute() { @@ -193,7 +195,7 @@ public function getBccAttribute() * * @return string */ - public function getSubject() + public function getSubject(): string { return $this->subject; } @@ -203,7 +205,7 @@ public function getSubject() * * @return string */ - public function getSubjectAttribute() + public function getSubjectAttribute(): string { return $this->subject; } @@ -213,7 +215,7 @@ public function getSubjectAttribute() * * @return string */ - public function getView() + public function getView(): string { return $this->view; } @@ -221,9 +223,9 @@ public function getView() /** * Get the e-mail variables. * - * @return array + * @return array|null */ - public function getVariables() + public function getVariables(): ?array { return $this->variables; } @@ -231,9 +233,9 @@ public function getVariables() /** * Get the e-mail variables. * - * @return array + * @return array|null */ - public function getVariablesAttribute() + public function getVariablesAttribute(): ?array { return $this->variables; } @@ -243,7 +245,7 @@ public function getVariablesAttribute() * * @return string */ - public function getBody() + public function getBody(): string { return $this->body; } @@ -253,7 +255,7 @@ public function getBody() * * @return string */ - public function getBodyAttribute() + public function getBodyAttribute(): string { return $this->body; } @@ -263,7 +265,7 @@ public function getBodyAttribute() * * @return array */ - public function getAttachments() + public function getAttachments(): array { return $this->attachments; } @@ -273,7 +275,7 @@ public function getAttachments() * * @return int */ - public function getAttempts() + public function getAttempts(): int { return $this->attempts; } @@ -281,9 +283,9 @@ public function getAttempts() /** * Get the queued date. * - * @return mixed + * @return string|null */ - public function getQueuedDate() + public function getQueuedDate(): ?string { return $this->queued_at; } @@ -293,7 +295,7 @@ public function getQueuedDate() * * @return Carbon */ - public function getQueuedDateAsCarbon() + public function getQueuedDateAsCarbon(): Carbon { if ($this->queued_at instanceof Carbon) { return $this->queued_at; @@ -305,9 +307,9 @@ public function getQueuedDateAsCarbon() /** * Get the scheduled date. * - * @return mixed + * @return string|null */ - public function getScheduledDate() + public function getScheduledDate(): ?string { return $this->scheduled_at; } @@ -317,7 +319,7 @@ public function getScheduledDate() * * @return bool */ - public function hasVariables() + public function hasVariables(): bool { return ! is_null($this->variables); } @@ -327,7 +329,7 @@ public function hasVariables() * * @return Carbon */ - public function getScheduledDateAsCarbon() + public function getScheduledDateAsCarbon(): Carbon { if ($this->scheduled_at instanceof Carbon) { return $this->scheduled_at; @@ -339,9 +341,9 @@ public function getScheduledDateAsCarbon() /** * Get the send date for this e-mail. * - * @return string + * @return string|null */ - public function getSendDate() + public function getSendDate(): ?string { return $this->sent_at; } @@ -349,9 +351,9 @@ public function getSendDate() /** * Get the send error. * - * @return string + * @return string|string */ - public function getError() + public function getError(): ?string { return $this->error; } @@ -361,7 +363,7 @@ public function getError() * * @return bool */ - public function hasFrom() + public function hasFrom(): bool { return is_array($this->from) && count($this->from) > 0; } @@ -371,7 +373,7 @@ public function hasFrom() * * @return bool */ - public function hasCc() + public function hasCc(): bool { return strlen($this->getRawDatabaseValue('cc')) > 0; } @@ -381,7 +383,7 @@ public function hasCc() * * @return bool */ - public function hasBcc() + public function hasBcc(): bool { return strlen($this->getRawDatabaseValue('bcc')) > 0; } @@ -391,7 +393,7 @@ public function hasBcc() * * @return bool */ - public function isScheduled() + public function isScheduled(): bool { return ! is_null($this->getScheduledDate()); } @@ -401,7 +403,7 @@ public function isScheduled() * * @return bool */ - public function isEncrypted() + public function isEncrypted(): bool { return (bool) $this->getRawDatabaseValue('encrypted'); } @@ -411,7 +413,7 @@ public function isEncrypted() * * @return bool */ - public function isSent() + public function isSent(): bool { return ! is_null($this->sent_at); } @@ -421,7 +423,7 @@ public function isSent() * * @return bool */ - public function hasFailed() + public function hasFailed(): bool { return $this->failed == 1; } @@ -431,7 +433,7 @@ public function hasFailed() * * @return void */ - public function markAsSending() + public function markAsSending(): void { $this->update([ 'attempts' => $this->attempts + 1, @@ -444,7 +446,7 @@ public function markAsSending() * * @return void */ - public function markAsSent() + public function markAsSent(): void { $now = Carbon::now()->toDateTimeString(); @@ -462,7 +464,7 @@ public function markAsSent() * @param Exception $exception * @return void */ - public function markAsFailed(Exception $exception) + public function markAsFailed(Exception $exception): void { $this->update([ 'sending' => 0, @@ -476,7 +478,7 @@ public function markAsFailed(Exception $exception) * * @return void */ - public function send() + public function send(): void { (new Sender)->send($this); } @@ -486,7 +488,7 @@ public function send() * * @return void */ - public function retry() + public function retry(): void { $retry = $this->replicate(); @@ -505,7 +507,12 @@ public function retry() $retry->save(); } - public function getRawDatabaseValue($key = null, $default = null) + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getRawDatabaseValue(string $key = null, $default = null) { if (method_exists($this, 'getRawOriginal')) { return $this->getRawOriginal($key, $default); diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 31e3397..aba011e 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -1,5 +1,7 @@ email; } @@ -45,9 +47,9 @@ public function getEmail() * * @param string $key * @param mixed $value - * @return static + * @return self */ - public function setData($key, $value) + public function setData(string $key, $value): self { $this->data[$key] = $value; @@ -61,7 +63,7 @@ public function setData($key, $value) * @param mixed $default * @return mixed */ - public function getData($key, $default = null) + public function getData(string $key, $default = null) { if (! is_null($default) && ! $this->hasData($key)) { return $default; @@ -76,7 +78,7 @@ public function getData($key, $default = null) * @param string $key * @return bool */ - public function hasData($key) + public function hasData(string $key): bool { return isset($this->data[$key]); } @@ -85,9 +87,9 @@ public function hasData($key) * Set the e-mail label. * * @param string $label - * @return static + * @return self */ - public function label($label) + public function label(string $label): self { return $this->setData('label', $label); } @@ -95,11 +97,11 @@ public function label($label) /** * Set the e-mail from address and aname. * - * @param array $address - * @param array $name - * @return static + * @param string|null $address + * @param string|null $name + * @return self */ - public function from($address = null, $name = null) + public function from(?string $address = null, ?string $name = null): self { return $this->setData('from', compact('address', 'name')); } @@ -108,9 +110,9 @@ public function from($address = null, $name = null) * Set the e-mail recipient(s). * * @param string|array $recipient - * @return static + * @return self */ - public function recipient($recipient) + public function recipient($recipient): self { return $this->setData('recipient', $recipient); } @@ -119,9 +121,9 @@ public function recipient($recipient) * Define the carbon-copy address(es). * * @param string|array $cc - * @return static + * @return self */ - public function cc($cc) + public function cc($cc): self { return $this->setData('cc', $cc); } @@ -130,9 +132,9 @@ public function cc($cc) * Define the blind carbon-copy address(es). * * @param string|array $bcc - * @return static + * @return self */ - public function bcc($bcc) + public function bcc($bcc): self { return $this->setData('bcc', $bcc); } @@ -141,9 +143,9 @@ public function bcc($bcc) * Set the e-mail subject. * * @param string $subject - * @return static + * @return self */ - public function subject($subject) + public function subject(string $subject): self { return $this->setData('subject', $subject); } @@ -152,9 +154,9 @@ public function subject($subject) * Set the e-mail view. * * @param string $view - * @return static + * @return self */ - public function view($view) + public function view(string $view): self { return $this->setData('view', $view); } @@ -163,9 +165,9 @@ public function view($view) * Set the e-mail variables. * * @param array $variables - * @return EmailComposer + * @return self */ - public function variables($variables) + public function variables(array $variables): self { return $this->setData('variables', $variables); } @@ -176,7 +178,7 @@ public function variables($variables) * @param mixed $scheduledAt * @return Email */ - public function schedule($scheduledAt) + public function schedule($scheduledAt): Email { return $this->later($scheduledAt); } @@ -187,7 +189,7 @@ public function schedule($scheduledAt) * @param mixed $scheduledAt * @return Email */ - public function later($scheduledAt) + public function later($scheduledAt): Email { $this->setData('scheduled_at', $scheduledAt); @@ -202,7 +204,7 @@ public function later($scheduledAt) * @param \DateTimeInterface|\DateInterval|int|null $delay * @return Email */ - public function queue($connection = null, $queue = null, $delay = null) + public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email { $connection = $connection ?: config('queue.default'); $queue = $queue ?: 'default'; @@ -219,9 +221,9 @@ public function queue($connection = null, $queue = null, $delay = null) * Set the Mailable. * * @param Mailable $mailable - * @return static + * @return self */ - public function mailable(Mailable $mailable) + public function mailable(Mailable $mailable): self { $this->setData('mailable', $mailable); @@ -235,16 +237,10 @@ public function mailable(Mailable $mailable) * * @param string $file * @param array $options - * @return static + * @return self */ - public function attach($file, $options = []) + public function attach(string $file, array $options = []): self { - $validFileName = (is_string($file) && strlen($file) > 0); - - if (! $validFileName) { - return $this; - } - $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; $attachments[] = compact('file', 'options'); @@ -258,16 +254,10 @@ public function attach($file, $options = []) * @param string $data * @param string $name * @param array $options - * @return $this + * @return self */ - public function attachData($data, $name, array $options = []) + public function attachData(string $data, string $name, array $options = []): self { - $validData = (is_string($data) && strlen($data) > 0); - - if (! $validData) { - return $this; - } - $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; $attachments[] = compact('data', 'name', 'options'); @@ -280,7 +270,7 @@ public function attachData($data, $name, array $options = []) * * @return Email */ - public function send() + public function send(): Email { (new Validator)->validate($this); diff --git a/src/Encrypter.php b/src/Encrypter.php index c0438a5..b61ef21 100644 --- a/src/Encrypter.php +++ b/src/Encrypter.php @@ -1,5 +1,7 @@ setEncrypted($composer); @@ -29,7 +31,7 @@ public function encrypt(EmailComposer $composer) * * @param EmailComposer $composer */ - private function setEncrypted(EmailComposer $composer) + private function setEncrypted(EmailComposer $composer): void { $composer->getEmail()->setAttribute('encrypted', 1); } @@ -39,7 +41,7 @@ private function setEncrypted(EmailComposer $composer) * * @param EmailComposer $composer */ - private function encryptRecipients(EmailComposer $composer) + private function encryptRecipients(EmailComposer $composer): void { $email = $composer->getEmail(); @@ -55,7 +57,7 @@ private function encryptRecipients(EmailComposer $composer) * * @param EmailComposer $composer */ - private function encryptFrom(EmailComposer $composer) + private function encryptFrom(EmailComposer $composer): void { $email = $composer->getEmail(); @@ -69,7 +71,7 @@ private function encryptFrom(EmailComposer $composer) * * @param EmailComposer $composer */ - private function encryptSubject(EmailComposer $composer) + private function encryptSubject(EmailComposer $composer): void { $email = $composer->getEmail(); @@ -83,7 +85,7 @@ private function encryptSubject(EmailComposer $composer) * * @param EmailComposer $composer */ - private function encryptVariables(EmailComposer $composer) + private function encryptVariables(EmailComposer $composer): void { if (! $composer->hasData('variables')) { return; @@ -101,7 +103,7 @@ private function encryptVariables(EmailComposer $composer) * * @param EmailComposer $composer */ - private function encryptBody(EmailComposer $composer) + private function encryptBody(EmailComposer $composer): void { $email = $composer->getEmail(); diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index a73984d..bdc2ebf 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -1,5 +1,7 @@ bootConfig(); $this->bootDatabase(); @@ -56,7 +58,7 @@ private function bootDatabase(): void * * @return void */ - public function register() + public function register(): void { $this->commands([ SendEmailsCommand::class, diff --git a/src/MailableReader.php b/src/MailableReader.php index d443351..f926540 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -1,5 +1,7 @@ call([$composer->getData('mailable'), 'build']); @@ -37,7 +39,7 @@ public function read(EmailComposer $composer) * @param string $from * @return array */ - private function convertMailableAddresses($from) + private function convertMailableAddresses($from): array { return collect($from)->map(function ($recipient) { return $recipient['address']; @@ -49,7 +51,7 @@ private function convertMailableAddresses($from) * * @param EmailComposer $composer */ - private function readRecipient(EmailComposer $composer) + private function readRecipient(EmailComposer $composer): void { $to = $this->convertMailableAddresses( $composer->getData('mailable')->to @@ -63,7 +65,7 @@ private function readRecipient(EmailComposer $composer) * * @param EmailComposer $composer */ - private function readFrom(EmailComposer $composer) + private function readFrom(EmailComposer $composer): void { $from = reset($composer->getData('mailable')->from); @@ -82,7 +84,7 @@ private function readFrom(EmailComposer $composer) * * @param EmailComposer $composer */ - private function readCc(EmailComposer $composer) + private function readCc(EmailComposer $composer): void { $cc = $this->convertMailableAddresses( $composer->getData('mailable')->cc @@ -96,7 +98,7 @@ private function readCc(EmailComposer $composer) * * @param EmailComposer $composer */ - private function readBcc(EmailComposer $composer) + private function readBcc(EmailComposer $composer): void { $bcc = $this->convertMailableAddresses( $composer->getData('mailable')->bcc @@ -110,7 +112,7 @@ private function readBcc(EmailComposer $composer) * * @param EmailComposer $composer */ - private function readSubject(EmailComposer $composer) + private function readSubject(EmailComposer $composer): void { $composer->subject($composer->getData('mailable')->subject); } @@ -121,7 +123,7 @@ private function readSubject(EmailComposer $composer) * @param EmailComposer $composer * @throws Exception */ - private function readBody(EmailComposer $composer) + private function readBody(EmailComposer $composer): void { if (app()->version() < '5.5') { throw new Exception('Mailables cannot be read by Laravel 5.4 and below. Sorry.'); @@ -139,7 +141,7 @@ private function readBody(EmailComposer $composer) * * @param EmailComposer $composer */ - private function readAttachments(EmailComposer $composer) + private function readAttachments(EmailComposer $composer): void { $mailable = $composer->getData('mailable'); diff --git a/src/MessageSent.php b/src/MessageSent.php index 527407e..3ca6e6e 100644 --- a/src/MessageSent.php +++ b/src/MessageSent.php @@ -1,5 +1,7 @@ prepareLabel($composer); @@ -45,7 +47,7 @@ public function prepare(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareLabel(EmailComposer $composer) + private function prepareLabel(EmailComposer $composer): void { if (! $composer->hasData('label')) { return; @@ -61,7 +63,7 @@ private function prepareLabel(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareRecipient(EmailComposer $composer) + private function prepareRecipient(EmailComposer $composer): void { if (Config::testing()) { $composer->recipient(Config::testEmailAddress()); @@ -77,7 +79,7 @@ private function prepareRecipient(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareFrom(EmailComposer $composer) + private function prepareFrom(EmailComposer $composer): void { $composer->getEmail()->fill([ 'from' => json_encode($composer->getData('from', '')), @@ -89,7 +91,7 @@ private function prepareFrom(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareCc(EmailComposer $composer) + private function prepareCc(EmailComposer $composer): void { if (Config::testing()) { $composer->setData('cc', []); @@ -105,7 +107,7 @@ private function prepareCc(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareBcc(EmailComposer $composer) + private function prepareBcc(EmailComposer $composer): void { if (Config::testing()) { $composer->setData('bcc', []); @@ -121,7 +123,7 @@ private function prepareBcc(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareSubject(EmailComposer $composer) + private function prepareSubject(EmailComposer $composer): void { $composer->getEmail()->fill([ 'subject' => $composer->getData('subject'), @@ -133,7 +135,7 @@ private function prepareSubject(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareView(EmailComposer $composer) + private function prepareView(EmailComposer $composer): void { $composer->getEmail()->fill([ 'view' => $composer->getData('view'), @@ -145,7 +147,7 @@ private function prepareView(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareVariables(EmailComposer $composer) + private function prepareVariables(EmailComposer $composer): void { if (! $composer->hasData('variables')) { return; @@ -161,7 +163,7 @@ private function prepareVariables(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareBody(EmailComposer $composer) + private function prepareBody(EmailComposer $composer): void { // If the body was predefined (by for example a mailable), use that. if ($composer->hasData('body')) { @@ -181,7 +183,7 @@ private function prepareBody(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareAttachments(EmailComposer $composer) + private function prepareAttachments(EmailComposer $composer): void { $attachments = []; @@ -209,7 +211,7 @@ private function prepareAttachments(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareScheduled(EmailComposer $composer) + private function prepareScheduled(EmailComposer $composer): void { if (! $composer->hasData('scheduled_at')) { return; @@ -231,7 +233,7 @@ private function prepareScheduled(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareImmediately(EmailComposer $composer) + private function prepareImmediately(EmailComposer $composer): void { if (Config::sendImmediately()) { $composer->getEmail()->fill(['sending' => 1]); @@ -243,7 +245,7 @@ private function prepareImmediately(EmailComposer $composer) * * @param EmailComposer $composer */ - private function prepareQueued(EmailComposer $composer) + private function prepareQueued(EmailComposer $composer): void { if ($composer->getData('queued', false) === true) { $composer->getEmail()->fill([ diff --git a/src/SendEmailJob.php b/src/SendEmailJob.php index 4ca3070..9bbd6f8 100644 --- a/src/SendEmailJob.php +++ b/src/SendEmailJob.php @@ -1,5 +1,6 @@ email = $email; } - public function handle() + public function handle(): void { (new Sender())->send($this->email); } diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index d1bca2d..039ca37 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -1,5 +1,7 @@ store->getQueue(); @@ -73,23 +75,13 @@ public function handle() $this->result($emails); } - /** - * Execute the console command (backwards compatibility for Laravel 5.4 and below). - * - * @return void - */ - public function fire() - { - $this->handle(); - } - /** * Output a table with the cronjob result. * * @param Collection $emails * @return void */ - protected function result($emails) + protected function result(Collection $emails): void { $headers = ['ID', 'Recipient', 'Subject', 'Status']; diff --git a/src/Sender.php b/src/Sender.php index 614a049..2a9bf63 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -1,5 +1,7 @@ isSent()) { return; @@ -39,7 +41,7 @@ public function send(Email $email) * @param Message $message * @param Email $email */ - private function buildMessage(Message $message, Email $email) + private function buildMessage(Message $message, Email $email): void { $message->to($email->getRecipient()) ->cc($email->hasCc() ? $email->getCc() : []) @@ -60,7 +62,7 @@ private function buildMessage(Message $message, Email $email) 'rawAttachment' => 'attachData', ]; - foreach ((array) $email->getAttachments() as $attachment) { + foreach ($email->getAttachments() as $attachment) { call_user_func_array([$message, $attachmentMap[$attachment['type']]], $attachment['attachment']); } } diff --git a/src/SentMessage.php b/src/SentMessage.php index 1abde4b..98c6156 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -1,5 +1,7 @@ validateLabel($composer); @@ -46,7 +48,7 @@ public function validate(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateLabel(EmailComposer $composer) + private function validateLabel(EmailComposer $composer): void { if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) { throw new InvalidArgumentException('The given label [' . $composer->getData('label') . '] is too large for database storage'); @@ -59,7 +61,7 @@ private function validateLabel(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateRecipient(EmailComposer $composer) + private function validateRecipient(EmailComposer $composer): void { if (! $composer->hasData('recipient')) { throw new InvalidArgumentException('No recipient specified'); @@ -84,7 +86,7 @@ private function validateRecipient(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateCc(EmailComposer $composer) + private function validateCc(EmailComposer $composer): void { if (! $composer->hasData('cc')) { return; @@ -103,7 +105,7 @@ private function validateCc(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateBcc(EmailComposer $composer) + private function validateBcc(EmailComposer $composer): void { if (! $composer->hasData('bcc')) { return; @@ -122,7 +124,7 @@ private function validateBcc(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateSubject(EmailComposer $composer) + private function validateSubject(EmailComposer $composer): void { if (! $composer->hasData('subject')) { throw new InvalidArgumentException('No subject specified'); @@ -135,7 +137,7 @@ private function validateSubject(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidARgumentException */ - private function validateView(EmailComposer $composer) + private function validateView(EmailComposer $composer): void { if ($composer->hasData('mailable')) { return; @@ -158,7 +160,7 @@ private function validateView(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateVariables(EmailComposer $composer) + private function validateVariables(EmailComposer $composer): void { if ($composer->hasData('variables') && ! is_array($composer->getData('variables'))) { throw new InvalidArgumentException('Variables must be an array'); @@ -171,7 +173,7 @@ private function validateVariables(EmailComposer $composer) * @param EmailComposer $composer * @throws InvalidArgumentException */ - private function validateScheduled(EmailComposer $composer) + private function validateScheduled(EmailComposer $composer): void { if (! $composer->hasData('scheduled_at')) { return; diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 697c745..686bd50 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -8,7 +8,6 @@ use Swift_Events_SendEvent; use Illuminate\Support\Facades\Mail; use Stackkit\LaravelDatabaseEmails\Email; -use Symfony\Component\Mime\Part\DataPart; class SenderTest extends TestCase { @@ -175,28 +174,6 @@ public function attachments_are_added_to_the_email() $this->assertCount(1, $attachments); } - /** @test */ - public function attachments_are_not_added_if_the_data_is_not_valid() - { - $this->sent = []; - $this->composeEmail()->attach(null)->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - - $this->sent = []; - $this->composeEmail()->attach(false)->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - - $this->sent = []; - $this->composeEmail()->attach('')->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - } - /** @test */ public function raw_attachments_are_added_to_the_email() { @@ -248,26 +225,4 @@ public function emails_can_be_sent_immediately() $this->artisan('email:send'); $this->assertCount(1, $this->sent); } - - /** @test */ - public function raw_attachments_are_not_added_if_the_data_is_not_valid() - { - $this->sent = []; - $this->composeEmail()->attachData(null, 'test.png')->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - - $this->sent = []; - $this->composeEmail()->attachData(false, 'test.png')->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - - $this->sent = []; - $this->composeEmail()->attachData('', 'test.png')->send(); - $this->artisan('email:send'); - $attachments = reset($this->sent)->attachments; - $this->assertCount(0, $attachments); - } } diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 0646c70..20f86a0 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -139,7 +139,7 @@ public function variables_must_be_defined_as_an_array() try { $email->variables($type)->send(); $this->fail('Expected exception to be thrown'); - } catch (InvalidArgumentException $e) { + } catch (\TypeError $e) { $this->assertEquals($e->getCode(), 0); } } From 535b88ef759d2fdafc311b41d0a76e26a5627da5 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 22:15:23 +0100 Subject: [PATCH 07/60] Apply some PSR-12 fixes --- src/Email.php | 4 ++-- src/EmailComposer.php | 8 ++++---- src/SendEmailJob.php | 5 ++++- src/Store.php | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Email.php b/src/Email.php index 042df33..4eba5ff 100644 --- a/src/Email.php +++ b/src/Email.php @@ -55,7 +55,7 @@ class Email extends Model */ public static function compose(): EmailComposer { - return new EmailComposer(new static); + return new EmailComposer(new static()); } /** @@ -480,7 +480,7 @@ public function markAsFailed(Exception $exception): void */ public function send(): void { - (new Sender)->send($this); + (new Sender())->send($this); } /** diff --git a/src/EmailComposer.php b/src/EmailComposer.php index aba011e..b358565 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -227,7 +227,7 @@ public function mailable(Mailable $mailable): self { $this->setData('mailable', $mailable); - (new MailableReader)->read($this); + (new MailableReader())->read($this); return $this; } @@ -272,12 +272,12 @@ public function attachData(string $data, string $name, array $options = []): sel */ public function send(): Email { - (new Validator)->validate($this); + (new Validator())->validate($this); - (new Preparer)->prepare($this); + (new Preparer())->prepare($this); if (Config::encryptEmails()) { - (new Encrypter)->encrypt($this); + (new Encrypter())->encrypt($this); } $this->email->save(); diff --git a/src/SendEmailJob.php b/src/SendEmailJob.php index 9bbd6f8..a7ba528 100644 --- a/src/SendEmailJob.php +++ b/src/SendEmailJob.php @@ -12,7 +12,10 @@ class SendEmailJob implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; public $email; diff --git a/src/Store.php b/src/Store.php index f15d62f..3a9f4f2 100644 --- a/src/Store.php +++ b/src/Store.php @@ -16,7 +16,7 @@ class Store */ public function getQueue(): Collection { - $query = new Email; + $query = new Email(); return $query ->whereNull('deleted_at') From 55208b5bee6ad2d5e85abc367c65eea7e2c07fe0 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 22:25:30 +0100 Subject: [PATCH 08/60] Drop support for Laravel 5 --- .github/workflows/run-tests.yml | 28 ++-------------------------- README.md | 6 ++---- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 347a5d2..063463c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: php: [8.1, 8.0, 7.4, 7.3, 7.2] - laravel: [9.*, 8.*, 7.*, 6.*, 5.8.*, 5.7.*, 5.6.*] + laravel: [9.*, 8.*, 7.*, 6.*] os: [ubuntu-latest] include: - laravel: 9.* @@ -23,12 +23,6 @@ jobs: testbench: 5.* - laravel: 6.* testbench: 4.* - - laravel: 5.8.* - testbench: 3.8.* - - laravel: 5.7.* - testbench: 3.7.* - - laravel: 5.6.* - testbench: 3.6.* exclude: - laravel: 9.* php: 7.2 @@ -38,24 +32,6 @@ jobs: php: 7.4 - laravel: 8.* php: 7.2 - - laravel: 5.7.* - php: 7.4 - - laravel: 5.6.* - php: 7.4 - - laravel: 5.5.* - php: 7.4 - - laravel: 5.8.* - php: 8.0 - - laravel: 5.7.* - php: 8.0 - - laravel: 5.6.* - php: 8.0 - - laravel: 5.6.* - php: 8.1 - - laravel: 5.7.* - php: 8.1 - - laravel: 5.8.* - php: 8.1 - laravel: 6.* php: 8.1 - laravel: 7.* @@ -99,4 +75,4 @@ jobs: CI_DB_DATABASE: test CI_DB_USERNAME: root CI_DB_PASSWORD: root - run: vendor/bin/phpunit \ No newline at end of file + run: vendor/bin/phpunit diff --git a/README.md b/README.md index edda785..457a323 100644 --- a/README.md +++ b/README.md @@ -19,18 +19,16 @@ We feel the package is currently feature complete, but feel free to send a pull # Requirements -This package requires Laravel 5.6 or higher. +This package requires Laravel 6.0 or higher. Please check the table below for supported Laravel and PHP versions: |Laravel Version| PHP Version | |---|---| -| 5.6 | 7.2 or 7.3 -| 5.7 | 7.2 or 7.3 -| 5.8 | 7.2 or 7.3 or 7.4 | 6.x | 7.2 or 7.3 or 7.4 or 8.0 | 7.x | 7.2 or 7.3 or 7.4 or 8.0 | 8.x | 7.3 or 7.4 or 8.0 or 8.1 +| 9.x | 8.0 or 8.1 # Installation From e835269fcf9408a95b715066fcab6cb89dacad46 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 10 Feb 2022 22:36:46 +0100 Subject: [PATCH 09/60] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 334ab01..e0b0367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 6.0.0 - 2022-02-10 + +**Added** + +- Added support for Laravel 9 with new Symfony Mailer instead of SwiftMail. + +**Changed** + +- Dropped support for Laravel 5.6, 5.7 and 5.8. + ## 5.0.0 - 2021-12-05 **Added** From 58ad83b52db229d1c69d04b56da5cf567a77029c Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Fri, 16 Sep 2022 19:34:05 +0200 Subject: [PATCH 10/60] Bump From 106364b96fa7eac127d4ebe631984861d9c7025d Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 8 Feb 2023 21:05:43 +0100 Subject: [PATCH 11/60] Add support for Laravel 10 --- .github/workflows/run-tests.yml | 35 ++++++++--------------- CHANGELOG.md | 7 +++++ README.md | 11 ++------ composer.json | 21 ++++++++++++-- phpunit.xml | 50 +++++++++++++-------------------- src/MailableReader.php | 4 --- 6 files changed, 59 insertions(+), 69 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 063463c..5474c95 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,28 +14,15 @@ jobs: php: [8.1, 8.0, 7.4, 7.3, 7.2] laravel: [9.*, 8.*, 7.*, 6.*] os: [ubuntu-latest] - include: - - laravel: 9.* - testbench: 7.* - - laravel: 8.* - testbench: 6.* - - laravel: 7.* - testbench: 5.* - - laravel: 6.* - testbench: 4.* - exclude: - - laravel: 9.* - php: 7.2 - - laravel: 9.* - php: 7.3 - - laravel: 9.* - php: 7.4 - - laravel: 8.* - php: 7.2 - - laravel: 6.* - php: 8.1 - - laravel: 7.* - php: 8.1 + payload: + - { queue: 'github-actions-laravel9-php81', laravel: '10.*', php: '8.2', 'testbench': '8.*' } + - { queue: 'github-actions-laravel9-php81', laravel: '10.*', php: '8.1', 'testbench': '8.*' } + - { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.2', 'testbench': '7.*' } + - { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.1', 'testbench': '7.*' } + - { queue: 'github-actions-laravel9-php80', laravel: '9.*', php: '8.0', 'testbench': '7.*' } + - { queue: 'github-actions-laravel8-php81', laravel: '8.*', php: '8.1', 'testbench': '6.*' } + - { queue: 'github-actions-laravel8-php80', laravel: '8.*', php: '8.0', 'testbench': '6.*' } + - { queue: 'github-actions-laravel8-php74', laravel: '8.*', php: '7.4', 'testbench': '6.*' } name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} @@ -54,7 +41,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v1 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -66,7 +53,7 @@ jobs: - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update - composer update --prefer-stable --prefer-dist --no-interaction --no-suggest + composer update --prefer-stable --prefer-dist --no-interaction - name: Execute tests env: CI_DB_DRIVER: mysql diff --git a/CHANGELOG.md b/CHANGELOG.md index e0b0367..407cef6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 6.1.0 - 2023-02-08 + +**Changed** + +- Added support for Laravel 10 +- Dropped support for Laravel 6 and 7 + ## 6.0.0 - 2022-02-10 **Added** diff --git a/README.md b/README.md index 457a323..bf3db92 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,9 @@ We feel the package is currently feature complete, but feel free to send a pull # Requirements -This package requires Laravel 6.0 or higher. +This package requires Laravel 8.0 or higher. -Please check the table below for supported Laravel and PHP versions: - -|Laravel Version| PHP Version | -|---|---| -| 6.x | 7.2 or 7.3 or 7.4 or 8.0 -| 7.x | 7.2 or 7.3 or 7.4 or 8.0 -| 8.x | 7.3 or 7.4 or 8.0 or 8.1 -| 9.x | 8.0 or 8.1 +Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions. # Installation diff --git a/composer.json b/composer.json index 583fb0d..eb56e5a 100644 --- a/composer.json +++ b/composer.json @@ -26,10 +26,27 @@ } }, "require": { - "ext-json": "*" + "ext-json": "*", + "illuminate/support": "^8.0|^9.0|^10.0" }, "require-dev": { "mockery/mockery": "^1.2", - "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0 || ^5.0" + "orchestra/testbench": "^6.0 || ^7.0 || ^8.0" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "l10": [ + "composer require laravel/framework:10.* orchestra/testbench:8.* --no-interaction --no-update", + "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + ], + "l9": [ + "composer require laravel/framework:9.* orchestra/testbench:7.* --no-interaction --no-update", + "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + ], + "l8": [ + "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", + "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + ] } } diff --git a/phpunit.xml b/phpunit.xml index 24fb777..eb3301d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,32 +1,22 @@ - - - - - ./tests/ - - - - - - - - - - - - - - - src/ - - + + + + src/ + + + + + ./tests/ + + + + + + + + + + + diff --git a/src/MailableReader.php b/src/MailableReader.php index f926540..f1307d6 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -125,10 +125,6 @@ private function readSubject(EmailComposer $composer): void */ private function readBody(EmailComposer $composer): void { - if (app()->version() < '5.5') { - throw new Exception('Mailables cannot be read by Laravel 5.4 and below. Sorry.'); - } - $composer->setData('view', ''); $mailable = $composer->getData('mailable'); From 9c8db84b4422f3aa02ca3706a7e5dc955c6a609f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 8 Feb 2023 21:09:59 +0100 Subject: [PATCH 12/60] Fix Github Actions yml --- .github/workflows/run-tests.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5474c95..682bc76 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -7,12 +7,10 @@ on: jobs: php-tests: - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.payload.os }} strategy: matrix: - php: [8.1, 8.0, 7.4, 7.3, 7.2] - laravel: [9.*, 8.*, 7.*, 6.*] os: [ubuntu-latest] payload: - { queue: 'github-actions-laravel9-php81', laravel: '10.*', php: '8.2', 'testbench': '8.*' } @@ -24,7 +22,7 @@ jobs: - { queue: 'github-actions-laravel8-php80', laravel: '8.*', php: '8.0', 'testbench': '6.*' } - { queue: 'github-actions-laravel8-php74', laravel: '8.*', php: '7.4', 'testbench': '6.*' } - name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} + name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} services: mysql: @@ -46,13 +44,13 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} + php-version: ${{ matrix.payload.php }} extensions: mbstring, dom, fileinfo, mysql coverage: none - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update composer update --prefer-stable --prefer-dist --no-interaction - name: Execute tests env: From add50fd5290485faf98916bd2484a2b4b6bf8d18 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 8 Feb 2023 21:13:53 +0100 Subject: [PATCH 13/60] Fix Github Actions yml --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 682bc76..c939408 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -7,7 +7,7 @@ on: jobs: php-tests: - runs-on: ${{ matrix.payload.os }} + runs-on: ${{ matrix.os }} strategy: matrix: From 1aa5c20ede0e36baec3b3971bb415692fcfe35a1 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Wed, 8 Feb 2023 21:38:52 +0100 Subject: [PATCH 14/60] Bring back support for Laravel 6 and 7 --- .github/workflows/run-tests.yml | 20 ++++++++++++-------- CHANGELOG.md | 1 - README.md | 2 +- composer.json | 13 ++++++++++--- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c939408..a74f70f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,14 +13,18 @@ jobs: matrix: os: [ubuntu-latest] payload: - - { queue: 'github-actions-laravel9-php81', laravel: '10.*', php: '8.2', 'testbench': '8.*' } - - { queue: 'github-actions-laravel9-php81', laravel: '10.*', php: '8.1', 'testbench': '8.*' } - - { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.2', 'testbench': '7.*' } - - { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.1', 'testbench': '7.*' } - - { queue: 'github-actions-laravel9-php80', laravel: '9.*', php: '8.0', 'testbench': '7.*' } - - { queue: 'github-actions-laravel8-php81', laravel: '8.*', php: '8.1', 'testbench': '6.*' } - - { queue: 'github-actions-laravel8-php80', laravel: '8.*', php: '8.0', 'testbench': '6.*' } - - { queue: 'github-actions-laravel8-php74', laravel: '8.*', php: '7.4', 'testbench': '6.*' } + - { laravel: '10.*', php: '8.2', 'testbench': '8.*'} + - { laravel: '10.*', php: '8.1', 'testbench': '8.*'} + - { laravel: '9.*', php: '8.2', 'testbench': '7.*'} + - { laravel: '9.*', php: '8.1', 'testbench': '7.*'} + - { laravel: '9.*', php: '8.0', 'testbench': '7.*'} + - { laravel: '8.*', php: '8.1', 'testbench': '6.*'} + - { laravel: '8.*', php: '8.0', 'testbench': '6.*'} + - { laravel: '8.*', php: '7.4', 'testbench': '6.*'} + - { laravel: '7.*', php: '8.0', 'testbench': '5.*' } + - { laravel: '7.*', php: '7.4', 'testbench': '5.*' } + - { laravel: '6.*', php: '8.0', 'testbench': '4.*' } + - { laravel: '6.*', php: '7.4', 'testbench': '4.*' } name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 407cef6..0b0cc84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. **Changed** - Added support for Laravel 10 -- Dropped support for Laravel 6 and 7 ## 6.0.0 - 2022-02-10 diff --git a/README.md b/README.md index bf3db92..7d08266 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ We feel the package is currently feature complete, but feel free to send a pull # Requirements -This package requires Laravel 8.0 or higher. +This package requires Laravel 6.0 or higher. Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions. diff --git a/composer.json b/composer.json index eb56e5a..48ff446 100644 --- a/composer.json +++ b/composer.json @@ -26,12 +26,11 @@ } }, "require": { - "ext-json": "*", - "illuminate/support": "^8.0|^9.0|^10.0" + "ext-json": "*" }, "require-dev": { "mockery/mockery": "^1.2", - "orchestra/testbench": "^6.0 || ^7.0 || ^8.0" + "orchestra/testbench": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "minimum-stability": "dev", "prefer-stable": true, @@ -47,6 +46,14 @@ "l8": [ "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + ], + "l7": [ + "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", + "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + ], + "l6": [ + "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", + "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" ] } } From 53f1c03b67c18348073d59a65806f132300620d5 Mon Sep 17 00:00:00 2001 From: Mahmud S <34059106+theeMahmud@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:40:46 +0600 Subject: [PATCH 15/60] fix: spelling mistake --- config/laravel-database-emails.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/laravel-database-emails.php b/config/laravel-database-emails.php index 8a7b553..6c179ef 100644 --- a/config/laravel-database-emails.php +++ b/config/laravel-database-emails.php @@ -51,7 +51,7 @@ | Cronjob Limit |-------------------------------------------------------------------------- | - | Limit the number of e-mails that should be sent at a time. Please ajust this + | Limit the number of e-mails that should be sent at a time. Please adjust this | configuration based on the number of e-mails you expect to send and | the throughput of your e-mail sending provider. | @@ -79,7 +79,7 @@ | | This option allows you to use: | `php artisan vendor:publish --tag=laravel-database-emails-migrations` to push migrations - | to your app's folder so you're free to modify before migrating. + | to your app's folder, so you're free to modify before migrating. | */ From 44db7d5424098bb6e14b7251d4347af17092c6a9 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 9 Apr 2023 20:09:04 +0200 Subject: [PATCH 16/60] Run pull request actions from source repo --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a74f70f..bb9f501 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,7 +1,7 @@ name: Run tests on: - pull_request: + pull_request_target: schedule: - cron: '0 0 * * *' From 4e8ec2ba678d35d6e1aa7e6c8cb92e1051d0a519 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 9 Apr 2023 20:53:20 +0200 Subject: [PATCH 17/60] Add support for new mailables --- composer.json | 10 ++--- src/MailableReader.php | 10 ++++- tests/MailableReaderTest.php | 71 +++++++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 48ff446..c3f929b 100644 --- a/composer.json +++ b/composer.json @@ -37,23 +37,23 @@ "scripts": { "l10": [ "composer require laravel/framework:10.* orchestra/testbench:8.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + "composer update --prefer-stable --prefer-dist --no-interaction" ], "l9": [ "composer require laravel/framework:9.* orchestra/testbench:7.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + "composer update --prefer-stable --prefer-dist --no-interaction" ], "l8": [ "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + "composer update --prefer-stable --prefer-dist --no-interaction" ], "l7": [ "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + "composer update --prefer-stable --prefer-dist --no-interaction" ], "l6": [ "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction --no-suggest" + "composer update --prefer-stable --prefer-dist --no-interaction" ] } } diff --git a/src/MailableReader.php b/src/MailableReader.php index f1307d6..bbd9b83 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -6,6 +6,7 @@ use Exception; use Illuminate\Container\Container; +use ReflectionObject; class MailableReader { @@ -16,7 +17,14 @@ class MailableReader */ public function read(EmailComposer $composer): void { - Container::getInstance()->call([$composer->getData('mailable'), 'build']); + if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { + $reflected = (new ReflectionObject($composer->getData('mailable'))); + $method = $reflected->getMethod('prepareMailableForDelivery'); + $method->setAccessible(true); + $method->invoke($composer->getData('mailable')); + } else { + Container::getInstance()->call([$composer->getData('mailable'), 'build']); + } $this->readRecipient($composer); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 173ea81..8cba2b2 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -3,21 +3,34 @@ namespace Tests; use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Attachment; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; use Stackkit\LaravelDatabaseEmails\Email; class MailableReaderTest extends TestCase { + private function mailable(): Mailable + { + if (version_compare(app()->version(), '10.0.0', '>=')) { + return new Laravel10TestMailable(); + } + + return new TestMailable(); + } + /** @test */ public function it_extracts_the_recipient() { $composer = Email::compose() - ->mailable(new TestMailable()); + ->mailable($this->mailable()); $this->assertEquals(['john@doe.com'], $composer->getData('recipient')); $composer = Email::compose() ->mailable( - (new TestMailable())->to(['jane@doe.com']) + $this->mailable()->to(['jane@doe.com']) ); $this->assertCount(2, $composer->getData('recipient')); @@ -28,7 +41,7 @@ public function it_extracts_the_recipient() /** @test */ public function it_extracts_cc_addresses() { - $composer = Email::compose()->mailable(new TestMailable()); + $composer = Email::compose()->mailable($this->mailable()); $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc')); } @@ -36,7 +49,7 @@ public function it_extracts_cc_addresses() /** @test */ public function it_extracts_bcc_addresses() { - $composer = Email::compose()->mailable(new TestMailable()); + $composer = Email::compose()->mailable($this->mailable()); $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); } @@ -44,7 +57,7 @@ public function it_extracts_bcc_addresses() /** @test */ public function it_extracts_the_subject() { - $composer = Email::compose()->mailable(new TestMailable()); + $composer = Email::compose()->mailable($this->mailable()); $this->assertEquals('Your order has shipped!', $composer->getData('subject')); } @@ -52,7 +65,7 @@ public function it_extracts_the_subject() /** @test */ public function it_extracts_the_body() { - $composer = Email::compose()->mailable(new TestMailable()); + $composer = Email::compose()->mailable($this->mailable()); $this->assertEquals("Name: John Doe\n", $composer->getData('body')); } @@ -60,7 +73,7 @@ public function it_extracts_the_body() /** @test */ public function it_extracts_attachments() { - $email = Email::compose()->mailable(new TestMailable())->send(); + $email = Email::compose()->mailable($this->mailable())->send(); $attachments = $email->getAttachments(); @@ -78,7 +91,7 @@ public function it_extracts_attachments() public function it_extracts_the_from_address_and_or_name() { $email = Email::compose()->mailable( - (new TestMailable()) + ($this->mailable()) ->from('marick@dolphiq.nl', 'Marick') )->send(); @@ -87,7 +100,7 @@ public function it_extracts_the_from_address_and_or_name() $this->assertEquals('Marick', $email->getFromName()); $email = Email::compose()->mailable( - (new TestMailable()) + ($this->mailable()) ->from('marick@dolphiq.nl') )->send(); @@ -96,7 +109,7 @@ public function it_extracts_the_from_address_and_or_name() $this->assertEquals(config('mail.from.name'), $email->getFromName()); $email = Email::compose()->mailable( - (new TestMailable()) + ($this->mailable()) ->from(null, 'Marick') )->send(); @@ -132,3 +145,41 @@ public function build() ->view('tests::dummy', ['name' => 'John Doe']); } } + +class Laravel10TestMailable extends Mailable +{ + public function content(): Content + { + $content = new Content( + 'tests::dummy' + ); + + $content->with('name', 'John Doe'); + + return $content; + } + + public function envelope(): Envelope + { + return new Envelope( + null, + [ + new Address('john@doe.com', 'John Doe') + ], + ['john+cc@doe.com', 'john+cc2@doe.com'], + ['john+bcc@doe.com', 'john+bcc2@doe.com'], + [], + 'Your order has shipped!' + ); + } + + public function attachments(): array + { + return [ + Attachment::fromPath(__DIR__ . '/files/pdf-sample.pdf')->withMime('application/pdf'), + Attachment::fromData(function () { + return '

Thanks for your oder

'; + }, 'order.html') + ]; + } +} From dc2b5e1baeb9c5ecc3b258aeeb26cb46377d468c Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 9 Apr 2023 21:07:42 +0200 Subject: [PATCH 18/60] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0cc84..c51e031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 6.2.0 - 2023-04-09 + +**Added** + +- Added support for Laravel 10 style mailables + ## 6.1.0 - 2023-02-08 **Changed** From f0561133b295904bed9364ae117fbeabf540550f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 10:04:21 +0100 Subject: [PATCH 19/60] Test package with PHP 8.3 --- .github/workflows/run-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index bb9f501..e389c1d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,8 +13,10 @@ jobs: matrix: os: [ubuntu-latest] payload: + - { laravel: '10.*', php: '8.3', 'testbench': '8.*'} - { laravel: '10.*', php: '8.2', 'testbench': '8.*'} - { laravel: '10.*', php: '8.1', 'testbench': '8.*'} + - { laravel: '9.*', php: '8.3', 'testbench': '7.*'} - { laravel: '9.*', php: '8.2', 'testbench': '7.*'} - { laravel: '9.*', php: '8.1', 'testbench': '7.*'} - { laravel: '9.*', php: '8.0', 'testbench': '7.*'} From 649eb3016b1b434caee260cd4e1e9581d9582280 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 10:35:51 +0100 Subject: [PATCH 20/60] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c51e031..1a2583d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 6.2.1 - 2023-12-28 + +**Changed** + +- Test package with PHP 8.3 + ## 6.2.0 - 2023-04-09 **Added** From 45836fa706de9dbd5d89bb27182897daa6fa813b Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 12:48:41 +0100 Subject: [PATCH 21/60] Add initial draft of ReplyTo feature --- .gitignore | 3 +- README.md | 11 +++++++ composer.json | 3 ++ ..._02_21000_add_reply_to_to_emails_table.php | 30 ++++++++++++++++++ src/Email.php | 31 +++++++++++++++++++ src/EmailComposer.php | 11 +++++++ src/Encrypter.php | 16 ++++++++++ src/HasEncryptedAttributes.php | 2 ++ src/MailableReader.php | 16 ++++++++++ src/Preparer.php | 14 +++++++++ src/Sender.php | 1 + src/SentMessage.php | 6 ++++ src/Validator.php | 22 +++++++++++++ tests/DatabaseInteractionTest.php | 14 +++++++++ tests/EncryptionTest.php | 12 +++++++ tests/MailableReaderTest.php | 11 ++++++- tests/SenderTest.php | 18 +++++++++++ tests/TestCase.php | 2 ++ tests/ValidatorTest.php | 15 +++++++++ 19 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php diff --git a/.gitignore b/.gitignore index 4218a0b..f11b2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ vendor/ .DS_Store composer.lock -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +.phpunit.cache diff --git a/README.md b/README.md index 7d08266..a9534a2 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,17 @@ Email::compose() ->bcc(['john@doe.com', 'jane@doe.com']); ``` +### Reply-To + +```php +replyTo(['john@doe.com', 'jane@doe.com']); +``` + ### Using mailables You may also pass a mailable to the e-mail composer. diff --git a/composer.json b/composer.json index c3f929b..1219696 100644 --- a/composer.json +++ b/composer.json @@ -54,6 +54,9 @@ "l6": [ "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", "composer update --prefer-stable --prefer-dist --no-interaction" + ], + "test": [ + "CI_DB_DRIVER=sqlite CI_DB_DATABASE=:memory: phpunit" ] } } diff --git a/database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php b/database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php new file mode 100644 index 0000000..09d4081 --- /dev/null +++ b/database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php @@ -0,0 +1,30 @@ +binary('reply_to')->nullable()->after('bcc'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/src/Email.php b/src/Email.php index 4eba5ff..f067025 100644 --- a/src/Email.php +++ b/src/Email.php @@ -15,6 +15,7 @@ * @property $from * @property $cc * @property $bcc + * @property $reply_to * @property $subject * @property $view * @property $variables @@ -190,6 +191,26 @@ public function getBccAttribute() return $this->bcc; } + /** + * Get the e-mail reply-to addresses. + * + * @return array|string + */ + public function getReplyTo() + { + return $this->reply_to; + } + + /** + * Get the e-mail reply-to addresses. + * + * @return array|string + */ + public function getReplyToAttribute() + { + return $this->reply_to; + } + /** * Get the e-mail subject. * @@ -388,6 +409,16 @@ public function hasBcc(): bool return strlen($this->getRawDatabaseValue('bcc')) > 0; } + /** + * Determine if the e-mail should sent with reply-to. + * + * @return bool + */ + public function hasReplyTo(): bool + { + return strlen($this->getRawDatabaseValue('reply_to') ?: '') > 0; + } + /** * Determine if the e-mail is scheduled to be sent later. * diff --git a/src/EmailComposer.php b/src/EmailComposer.php index b358565..352e63e 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -139,6 +139,17 @@ public function bcc($bcc): self return $this->setData('bcc', $bcc); } + /** + * Define the reply-to address(es). + * + * @param string|array $replyTo + * @return self + */ + public function replyTo($replyTo): self + { + return $this->setData('reply_to', $replyTo); + } + /** * Set the e-mail subject. * diff --git a/src/Encrypter.php b/src/Encrypter.php index b61ef21..14052a7 100644 --- a/src/Encrypter.php +++ b/src/Encrypter.php @@ -17,6 +17,8 @@ public function encrypt(EmailComposer $composer): void $this->encryptRecipients($composer); + $this->encryptReplyTo($composer); + $this->encryptFrom($composer); $this->encryptSubject($composer); @@ -36,6 +38,20 @@ private function setEncrypted(EmailComposer $composer): void $composer->getEmail()->setAttribute('encrypted', 1); } + /** + * Encrypt the e-mail reply-to. + * + * @param EmailComposer $composer + */ + private function encryptReplyTo(EmailComposer $composer): void + { + $email = $composer->getEmail(); + + $email->fill([ + 'reply_to' => $composer->hasData('reply_to') ? encrypt($email->reply_to) : '', + ]); + } + /** * Encrypt the e-mail addresses of the recipients. * diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index bdc2ebf..01dfbb4 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -18,6 +18,7 @@ trait HasEncryptedAttributes 'from', 'cc', 'bcc', + 'reply_to', 'subject', 'variables', 'body', @@ -33,6 +34,7 @@ trait HasEncryptedAttributes 'from', 'cc', 'bcc', + 'reply_to', 'variables', ]; diff --git a/src/MailableReader.php b/src/MailableReader.php index bbd9b83..1a796ec 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -34,6 +34,8 @@ public function read(EmailComposer $composer): void $this->readBcc($composer); + $this->readReplyTo($composer); + $this->readSubject($composer); $this->readBody($composer); @@ -115,6 +117,20 @@ private function readBcc(EmailComposer $composer): void $composer->bcc($bcc); } + /** + * Read the mailable reply-to to the email composer. + * + * @param EmailComposer $composer + */ + private function readReplyTo(EmailComposer $composer): void + { + $replyTo = $this->convertMailableAddresses( + $composer->getData('mailable')->replyTo + ); + + $composer->replyTo($replyTo); + } + /** * Read the mailable subject to the email composer. * diff --git a/src/Preparer.php b/src/Preparer.php index 831e0c0..eaca5bb 100644 --- a/src/Preparer.php +++ b/src/Preparer.php @@ -25,6 +25,8 @@ public function prepare(EmailComposer $composer): void $this->prepareBcc($composer); + $this->prepareReplyTo($composer); + $this->prepareSubject($composer); $this->prepareView($composer); @@ -118,6 +120,18 @@ private function prepareBcc(EmailComposer $composer): void ]); } + /** + * Prepare the reply-to for database storage. + * + * @param EmailComposer $composer + */ + private function prepareReplyTo(EmailComposer $composer): void + { + $composer->getEmail()->fill([ + 'reply_to' => json_encode($composer->getData('reply_to', [])), + ]); + } + /** * Prepare the subject for database storage. * diff --git a/src/Sender.php b/src/Sender.php index 2a9bf63..60a80fe 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -46,6 +46,7 @@ private function buildMessage(Message $message, Email $email): void $message->to($email->getRecipient()) ->cc($email->hasCc() ? $email->getCc() : []) ->bcc($email->hasBcc() ? $email->getBcc() : []) + ->replyTo($email->hasReplyTo() ? $email->getReplyTo() : []) ->subject($email->getSubject()) ->from($email->getFromAddress(), $email->getFromName()); diff --git a/src/SentMessage.php b/src/SentMessage.php index 98c6156..570d524 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -13,6 +13,7 @@ class SentMessage public $to = []; public $cc = []; public $bcc = []; + public $replyTo = []; public $subject = ''; public $body = ''; public $attachments = []; @@ -38,6 +39,10 @@ public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $em $sentMessage->bcc[$address->getAddress()] = $address->getName(); } + foreach ($email->getReplyTo() as $address) { + $sentMessage->replyTo[$address->getAddress()] = $address->getName(); + } + $sentMessage->subject = $email->getSubject(); $sentMessage->body = $email->getHtmlBody(); $sentMessage->attachments = array_map(function (DataPart $dataPart) { @@ -58,6 +63,7 @@ public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message) $sentMessage->to = $message->getTo(); $sentMessage->cc = $message->getCc(); $sentMessage->bcc = $message->getBcc(); + $sentMessage->replyTo = $message->getReplyTo(); $sentMessage->subject = $message->getSubject(); $sentMessage->body = $message->getBody(); $sentMessage->attachments = array_map(function(Swift_Mime_SimpleMimeEntity $entity) { diff --git a/src/Validator.php b/src/Validator.php index 038c484..50ea9f0 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -7,6 +7,7 @@ use Exception; use Carbon\Carbon; use InvalidArgumentException; +use const FILTER_VALIDATE_EMAIL; class Validator { @@ -33,6 +34,8 @@ public function validate(EmailComposer $composer): void $this->validateBcc($composer); + $this->validateReplyTo($composer); + $this->validateSubject($composer); $this->validateView($composer); @@ -118,6 +121,25 @@ private function validateBcc(EmailComposer $composer): void } } + /** + * Validate the reply-to addresses. + * + * @param EmailComposer $composer + * @throws InvalidArgumentException + */ + private function validateReplyTo(EmailComposer $composer): void + { + if (! $composer->hasData('reply_to')) { + return; + } + + foreach ((array) $composer->getData('reply_to') as $replyTo) { + if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { + throw new InvalidargumentException('E-mail address [' . $replyTo . '] is invalid'); + } + } + } + /** * Validate the e-mail subject. * diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 54dfdbf..1bc8d71 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -44,6 +44,20 @@ public function cc_and_bcc_should_be_saved_correctly() $this->assertEquals(['jane@doe.com'], $email->getBcc()); } + /** @test */ + public function reply_to_should_be_saved_correctly() + { + $email = $this->sendEmail([ + 'reply_to' => $replyTo = [ + 'john@doe.com', + ], + ]); + + $this->assertEquals(json_encode($replyTo), DB::table('emails')->find(1)->reply_to); + $this->assertTrue($email->hasReplyTo()); + $this->assertEquals(['john@doe.com'], $email->getReplyTo()); + } + /** @test */ public function subject_should_be_saved_correclty() { diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index e3f6c01..3339897 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -46,6 +46,18 @@ public function cc_and_bb_should_be_encrypted_and_decrypted() $this->assertEquals($bcc, $email->getBcc()); } + /** @test */ + public function reply_to_should_be_encrypted_and_decrypted() + { + $email = $this->sendEmail([ + 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], + ]); + + $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); + + $this->assertEquals($replyTo, $email->getReplyTo()); + } + /** @test */ public function the_subject_should_be_encrypted_and_decrypted() { diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 8cba2b2..20e7e45 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -54,6 +54,14 @@ public function it_extracts_bcc_addresses() $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); } + /** @test */ + public function it_extracts_reply_to_addresses() + { + $composer = Email::compose()->mailable($this->mailable()); + + $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to')); + } + /** @test */ public function it_extracts_the_subject() { @@ -137,6 +145,7 @@ public function build() return $this->to('john@doe.com') ->cc(['john+cc@doe.com', 'john+cc2@doe.com']) ->bcc(['john+bcc@doe.com', 'john+bcc2@doe.com']) + ->replyTo(['replyto@example.com', 'replyto2@example.com']) ->subject('Your order has shipped!') ->attach(__DIR__ . '/files/pdf-sample.pdf', [ 'mime' => 'application/pdf', @@ -168,7 +177,7 @@ public function envelope(): Envelope ], ['john+cc@doe.com', 'john+cc2@doe.com'], ['john+bcc@doe.com', 'john+bcc2@doe.com'], - [], + ['replyto@example.com', 'replyto2@example.com'], 'Your order has shipped!' ); } diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 686bd50..05b645c 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -225,4 +225,22 @@ public function emails_can_be_sent_immediately() $this->artisan('email:send'); $this->assertCount(1, $this->sent); } + + /** @test */ + public function it_adds_the_reply_to_addresses() + { + $this->sendEmail(['reply_to' => 'replyto@test.com']); + $this->artisan('email:send'); + $replyTo = reset($this->sent)->replyTo; + $this->assertCount(1, $replyTo); + $this->assertArrayHasKey('replyto@test.com', $replyTo); + + $this->sent = []; + $this->sendEmail(['reply_to' => ['replyto1@test.com', 'replyto2@test.com']]); + $this->artisan('email:send'); + $replyTo = reset($this->sent)->replyTo; + $this->assertCount(2, $replyTo); + $this->assertArrayHasKey('replyto1@test.com', $replyTo); + $this->assertArrayHasKey('replyto2@test.com', $replyTo); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 6223864..482aded 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -82,6 +82,7 @@ public function createEmail($overwrite = []) 'recipient' => 'john@doe.com', 'cc' => null, 'bcc' => null, + 'reply_to' => null, 'subject' => 'test', 'view' => 'tests::dummy', 'variables' => ['name' => 'John Doe'], @@ -92,6 +93,7 @@ public function createEmail($overwrite = []) ->recipient($params['recipient']) ->cc($params['cc']) ->bcc($params['bcc']) + ->replyTo($params['reply_to']) ->subject($params['subject']) ->view($params['view']) ->variables($params['variables']); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 20f86a0..b8b02ce 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -80,6 +80,21 @@ public function bcc_must_contain_valid_email_addresses() ->send(); } + /** @test */ + public function reply_to_must_contain_valid_email_addresses() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); + + Email::compose() + ->recipient('john@doe.com') + ->replyTo([ + 'jane@doe.com', + 'not-a-valid-email-address', + ]) + ->send(); + } + /** @test */ public function a_subject_is_required() { From 75be2567f28cdcd46982c8f7d396d82ede95e000 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 14:52:37 +0100 Subject: [PATCH 22/60] Fix mailable reader storing view object instead of view contents --- src/MailableReader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MailableReader.php b/src/MailableReader.php index 1a796ec..86be654 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -153,7 +153,7 @@ private function readBody(EmailComposer $composer): void $mailable = $composer->getData('mailable'); - $composer->setData('body', view($mailable->view, $mailable->buildViewData())); + $composer->setData('body', view($mailable->view, $mailable->buildViewData())->render()); } /** From a64567f9cb89c31d2ab7bb78d7b8ee4e50660f50 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 15:43:14 +0100 Subject: [PATCH 23/60] Add support for Address object in ReplyTo --- src/Preparer.php | 18 +++++++++++++++++- src/Validator.php | 8 +++++++- tests/SenderTest.php | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Preparer.php b/src/Preparer.php index eaca5bb..ea405d0 100644 --- a/src/Preparer.php +++ b/src/Preparer.php @@ -5,6 +5,7 @@ namespace Stackkit\LaravelDatabaseEmails; use Carbon\Carbon; +use Illuminate\Mail\Mailables\Address; class Preparer { @@ -127,8 +128,23 @@ private function prepareBcc(EmailComposer $composer): void */ private function prepareReplyTo(EmailComposer $composer): void { + $value = $composer->getData('reply_to', []); + + if (! is_array($value)) { + $value = [$value]; + } + + foreach ($value as $i => $v) { + if ($v instanceof Address) { + $value[$i] = [ + 'address' => $v->address, + 'name' => $v->name, + ]; + } + } + $composer->getEmail()->fill([ - 'reply_to' => json_encode($composer->getData('reply_to', [])), + 'reply_to' => json_encode($value), ]); } diff --git a/src/Validator.php b/src/Validator.php index 50ea9f0..69d130f 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -6,6 +6,8 @@ use Exception; use Carbon\Carbon; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Support\Arr; use InvalidArgumentException; use const FILTER_VALIDATE_EMAIL; @@ -133,7 +135,11 @@ private function validateReplyTo(EmailComposer $composer): void return; } - foreach ((array) $composer->getData('reply_to') as $replyTo) { + foreach (Arr::wrap($composer->getData('reply_to')) as $replyTo) { + if ($replyTo instanceof Address) { + $replyTo = $replyTo->address; + } + if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { throw new InvalidargumentException('E-mail address [' . $replyTo . '] is invalid'); } diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 05b645c..eb86b66 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -2,6 +2,7 @@ namespace Tests; +use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Event; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; @@ -242,5 +243,36 @@ public function it_adds_the_reply_to_addresses() $this->assertCount(2, $replyTo); $this->assertArrayHasKey('replyto1@test.com', $replyTo); $this->assertArrayHasKey('replyto2@test.com', $replyTo); + + if (! class_exists(Address::class)) { + return; + } + + $this->sent = []; + $this->sendEmail([ + 'reply_to' => new Address('replyto@test.com', 'NoReplyTest'), + ]); + $this->artisan('email:send'); + $replyTo = reset($this->sent)->replyTo; + $this->assertCount(1, $replyTo); + $this->assertSame(['replyto@test.com' => 'NoReplyTest'], $replyTo); + + $this->sent = []; + $this->sendEmail([ + 'reply_to' => [ + new Address('replyto@test.com', 'NoReplyTest'), + new Address('replyto2@test.com', 'NoReplyTest2'), + ], + ]); + $this->artisan('email:send'); + $replyTo = reset($this->sent)->replyTo; + $this->assertCount(2, $replyTo); + $this->assertSame( + [ + 'replyto@test.com' => 'NoReplyTest', + 'replyto2@test.com' => 'NoReplyTest2', + ], + $replyTo + ); } } From 371e9222fc12c4fae98cb75cc8791c27d9012152 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 15:55:03 +0100 Subject: [PATCH 24/60] Expand test suite --- tests/EncryptionTest.php | 25 +++++++++++++++++++++++-- tests/MailableReaderTest.php | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index 3339897..36a9328 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -2,6 +2,8 @@ namespace Tests; +use Illuminate\Mail\Mailables\Address; + class EncryptionTest extends TestCase { public function setUp(): void @@ -52,10 +54,29 @@ public function reply_to_should_be_encrypted_and_decrypted() $email = $this->sendEmail([ 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], ]); - $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertEquals($replyTo, $email->getReplyTo()); + + if (! class_exists(Address::class)) { + return; + } + + // Test with a single Address object... + $email = $this->sendEmail([ + 'reply_to' => new Address('john+1@doe.com', 'John Doe'), + ]); + $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); + $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo()); + + // Address with an array of Address objects... + $email = $this->sendEmail([ + 'reply_to' => [ + new Address('john+1@doe.com', 'John Doe'), + new Address('jane+1@doe.com', 'Jane Doe'), + ], + ]); + $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); + $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo()); } /** @test */ diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 20e7e45..c8fbd11 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -177,7 +177,7 @@ public function envelope(): Envelope ], ['john+cc@doe.com', 'john+cc2@doe.com'], ['john+bcc@doe.com', 'john+bcc2@doe.com'], - ['replyto@example.com', 'replyto2@example.com'], + ['replyto@example.com', new Address('replyto2@example.com')], 'Your order has shipped!' ); } From 31a4d908d42d9068af2782bc05b6e295b5cb8839 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 16:03:53 +0100 Subject: [PATCH 25/60] Update docs --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a9534a2..816a804 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,15 @@ use Stackkit\LaravelDatabaseEmails\Email; Email::compose() ->replyTo(['john@doe.com', 'jane@doe.com']); + +Email::compose() + ->replyTo(new Address('john@doe.com', 'John Doe')); + +Email::compose() + ->replyTo([ + new Address('john@doe.com', 'John Doe'), + new Address('jane@doe.com', 'Jane Doe'), + ]); ``` ### Using mailables From eee90f98a866e249d60bdb9c51a9d90884510353 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 16:53:06 +0100 Subject: [PATCH 26/60] Make it possible to prune Email models --- README.md | 25 +++++++++++++++++++++++ src/Email.php | 27 +++++++++++++++++++++++++ tests/PruneTest.php | 49 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 tests/PruneTest.php diff --git a/README.md b/README.md index 816a804..0dc8cdf 100644 --- a/README.md +++ b/README.md @@ -252,3 +252,28 @@ To enable, set the following environment variable: ``` LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY=true ``` + +### Pruning models + +```php +use Stackkit\LaravelDatabaseEmails\Email; + +$schedule->command('model:prune', [ + '--model' => [Email::class], +])->everyMinute(); +``` + +By default, e-mails are pruned when they are older than 6 months. + +You may change that by adding the following to the AppServiceProvider.php: + +```php +use Stackkit\LaravelDatabaseEmails\Email; + +public function register(): void +{ + Email::pruneWhen(function (Email $email) { + return $email->where(...); + }); +} +``` diff --git a/src/Email.php b/src/Email.php index f067025..f24831f 100644 --- a/src/Email.php +++ b/src/Email.php @@ -4,9 +4,12 @@ namespace Stackkit\LaravelDatabaseEmails; +use Closure; use Exception; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Prunable; /** * @property $id @@ -34,6 +37,7 @@ class Email extends Model { use HasEncryptedAttributes; + use Prunable; /** * The table in which the e-mails are stored. @@ -49,6 +53,8 @@ class Email extends Model */ protected $guarded = []; + public static ?Closure $pruneQuery = null; + /** * Compose a new e-mail. * @@ -551,4 +557,25 @@ public function getRawDatabaseValue(string $key = null, $default = null) return $this->getOriginal($key, $default); } + + /** + * @param Closure $closure + * @return void + */ + public static function pruneWhen(Closure $closure) + { + static::$pruneQuery = $closure; + } + + /** + * @return Builder + */ + public function prunable() + { + if (static::$pruneQuery) { + return (static::$pruneQuery)($this); + } + + return $this->where('created_at', '<', now()->subMonths(6)); + } } diff --git a/tests/PruneTest.php b/tests/PruneTest.php new file mode 100644 index 0000000..03a51cc --- /dev/null +++ b/tests/PruneTest.php @@ -0,0 +1,49 @@ +sendEmail(); + + Carbon::setTestNow($email->created_at . ' + 6 months'); + $this->artisan('model:prune', ['--model' => [Email::class]]); + $this->assertInstanceOf(Email::class, $email->fresh()); + + Carbon::setTestNow($email->created_at . ' + 6 months + 1 day'); + + // Ensure the email object has to be passed manually, otherwise we are acidentally + // deleting everyone's e-mails... + $this->artisan('model:prune'); + $this->assertInstanceOf(Email::class, $email->fresh()); + + // Now test with it passed... then it should definitely be deleted. + $this->artisan('model:prune', ['--model' => [Email::class]]); + $this->assertNull($email->fresh()); + } + + /** @test */ + public function can_change_when_emails_are_pruned() + { + Email::pruneWhen(function (Email $email) { + return $email->where('created_at', '<', now()->subMonths(3)); + }); + + $email = $this->sendEmail(); + + Carbon::setTestNow($email->created_at . ' + 3 months'); + $this->artisan('model:prune', ['--model' => [Email::class]]); + $this->assertInstanceOf(Email::class, $email->fresh()); + + Carbon::setTestNow($email->created_at . ' + 3 months + 1 day'); + $this->artisan('model:prune', ['--model' => [Email::class]]); + $this->assertNull($email->fresh()); + } +} From 61a0bad59fc347276282c5c00e8c3f8df3289afb Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 28 Dec 2023 20:42:00 +0100 Subject: [PATCH 27/60] Rename migration --- ...ble.php => 2023_12_28_140000_add_reply_to_to_emails_table.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename database/migrations/{2021_12_02_21000_add_reply_to_to_emails_table.php => 2023_12_28_140000_add_reply_to_to_emails_table.php} (100%) diff --git a/database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php b/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php similarity index 100% rename from database/migrations/2021_12_02_21000_add_reply_to_to_emails_table.php rename to database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php From 20755ce18f49e405d4ad63a87c9781d88ad0037a Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 30 Dec 2023 15:43:14 +0100 Subject: [PATCH 28/60] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2583d..07cbd09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 6.3.0 - 2023-12-30 + +**Added** + +- ReplyTo feature + ## 6.2.1 - 2023-12-28 **Changed** From b56cedd350492024397ebe06c2fabc5bdc91c913 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 13:41:14 +0100 Subject: [PATCH 29/60] Prepare run-test workflow --- .github/workflows/run-tests.yml | 17 ++++------------- composer.json | 28 ++++++++-------------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e389c1d..e6bb5db 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,26 +13,17 @@ jobs: matrix: os: [ubuntu-latest] payload: + - { laravel: '11.*', php: '8.3', 'testbench': '9.*' } + - { laravel: '11.*', php: '8.2', 'testbench': '9.*' } - { laravel: '10.*', php: '8.3', 'testbench': '8.*'} - { laravel: '10.*', php: '8.2', 'testbench': '8.*'} - { laravel: '10.*', php: '8.1', 'testbench': '8.*'} - - { laravel: '9.*', php: '8.3', 'testbench': '7.*'} - - { laravel: '9.*', php: '8.2', 'testbench': '7.*'} - - { laravel: '9.*', php: '8.1', 'testbench': '7.*'} - - { laravel: '9.*', php: '8.0', 'testbench': '7.*'} - - { laravel: '8.*', php: '8.1', 'testbench': '6.*'} - - { laravel: '8.*', php: '8.0', 'testbench': '6.*'} - - { laravel: '8.*', php: '7.4', 'testbench': '6.*'} - - { laravel: '7.*', php: '8.0', 'testbench': '5.*' } - - { laravel: '7.*', php: '7.4', 'testbench': '5.*' } - - { laravel: '6.*', php: '8.0', 'testbench': '4.*' } - - { laravel: '6.*', php: '7.4', 'testbench': '4.*' } name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} services: mysql: - image: mysql:5.7.27 + image: mysql:8 env: MYSQL_USER: root MYSQL_ROOT_PASSWORD: root @@ -66,4 +57,4 @@ jobs: CI_DB_DATABASE: test CI_DB_USERNAME: root CI_DB_PASSWORD: root - run: vendor/bin/phpunit + run: composer test diff --git a/composer.json b/composer.json index 1219696..ed7846f 100644 --- a/composer.json +++ b/composer.json @@ -26,34 +26,22 @@ } }, "require": { - "ext-json": "*" + "ext-json": "*", + "laravel/framework": "^10.0|^11.0" }, "require-dev": { "mockery/mockery": "^1.2", - "orchestra/testbench": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + "orchestra/testbench": "^8.0|^9.0", + "laravel/pint": "^1.14" }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { - "l10": [ - "composer require laravel/framework:10.* orchestra/testbench:8.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l9": [ - "composer require laravel/framework:9.* orchestra/testbench:7.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l8": [ - "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" + "l11": [ + "composer update laravel/framework:11.* orchestra/testbench:9.* --with-all-dependencies" ], - "l7": [ - "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l6": [ - "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" + "l10": [ + "composer update laravel/framework:10.* orchestra/testbench:8.* --with-all-dependencies" ], "test": [ "CI_DB_DRIVER=sqlite CI_DB_DATABASE=:memory: phpunit" From 0aaf57b7329ce57a970be21eac49a4067fcff590 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 13:53:54 +0100 Subject: [PATCH 30/60] Remove support for older Laravel versions --- src/Sender.php | 13 +++-------- src/SentMessage.php | 25 ++------------------- tests/MailableReaderTest.php | 36 +----------------------------- tests/SenderTest.php | 17 +++++--------- tests/TestingMailEventListener.php | 22 ------------------ 5 files changed, 12 insertions(+), 101 deletions(-) delete mode 100644 tests/TestingMailEventListener.php diff --git a/src/Sender.php b/src/Sender.php index 60a80fe..72ce8a1 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -26,9 +26,8 @@ public function send(Email $email): void $this->buildMessage($message, $email); }); - // This is used so we can assert things on the sent message in Laravel 9+ since we cannot use - // the Swift Mailer plugin anymore. So this is purely used for in the PHPUnit tests. - if (version_compare(app()->version(), '9.0.0', '>=') && !is_null($sentMessage)) { + // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) + if (!is_null($sentMessage)) { event(new MessageSent($sentMessage)); } @@ -50,13 +49,7 @@ private function buildMessage(Message $message, Email $email): void ->subject($email->getSubject()) ->from($email->getFromAddress(), $email->getFromName()); - if (version_compare(app()->version(), '9.0.0', '>=')) { - // Symfony Mailer - $message->html($email->getBody()); - } else { - // SwiftMail - $message->setBody($email->getBody(), 'text/html'); - } + $message->html($email->getBody()); $attachmentMap = [ 'attachment' => 'attach', diff --git a/src/SentMessage.php b/src/SentMessage.php index 570d524..c58bbdb 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -4,7 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; -use Swift_Mime_SimpleMimeEntity; +use Symfony\Component\Mime\Email; use Symfony\Component\Mime\Part\DataPart; class SentMessage @@ -19,7 +19,7 @@ class SentMessage public $attachments = []; public $headers = []; - public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $email): SentMessage + public static function createFromSymfonyMailer(Email $email): SentMessage { $sentMessage = new self(); @@ -54,25 +54,4 @@ public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $em return $sentMessage; } - - public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message): SentMessage - { - $sentMessage = new self(); - - $sentMessage->from = $message->getFrom(); - $sentMessage->to = $message->getTo(); - $sentMessage->cc = $message->getCc(); - $sentMessage->bcc = $message->getBcc(); - $sentMessage->replyTo = $message->getReplyTo(); - $sentMessage->subject = $message->getSubject(); - $sentMessage->body = $message->getBody(); - $sentMessage->attachments = array_map(function(Swift_Mime_SimpleMimeEntity $entity) { - return [ - 'body' => $entity->getBody(), - 'disposition' => $entity->getContentType() . ' ' . $entity->getHeaders()->get('content-disposition'), - ]; - }, $message->getChildren()); - - return $sentMessage; - } } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index c8fbd11..1fc6acb 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -13,10 +13,6 @@ class MailableReaderTest extends TestCase { private function mailable(): Mailable { - if (version_compare(app()->version(), '10.0.0', '>=')) { - return new Laravel10TestMailable(); - } - return new TestMailable(); } @@ -121,41 +117,11 @@ public function it_extracts_the_from_address_and_or_name() ->from(null, 'Marick') )->send(); - // 8.x no longer accepts an empty address. - // https://github.com/laravel/framework/pull/39035 - if (version_compare(app()->version(), '8.0.0', '>=')) { - $this->assertFalse($email->hasFrom()); - } else { - $this->assertTrue($email->hasFrom()); - $this->assertEquals(config('mail.from.address'), $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); - } + $this->assertFalse($email->hasFrom()); } } class TestMailable extends Mailable -{ - /** - * Build the message. - * - * @return $this - */ - public function build() - { - return $this->to('john@doe.com') - ->cc(['john+cc@doe.com', 'john+cc2@doe.com']) - ->bcc(['john+bcc@doe.com', 'john+bcc2@doe.com']) - ->replyTo(['replyto@example.com', 'replyto2@example.com']) - ->subject('Your order has shipped!') - ->attach(__DIR__ . '/files/pdf-sample.pdf', [ - 'mime' => 'application/pdf', - ]) - ->attachData('

Thanks for your oder

', 'order.html') - ->view('tests::dummy', ['name' => 'John Doe']); - } -} - -class Laravel10TestMailable extends Mailable { public function content(): Content { diff --git a/tests/SenderTest.php b/tests/SenderTest.php index eb86b66..1745d45 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -6,28 +6,23 @@ use Illuminate\Support\Facades\Event; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; -use Swift_Events_SendEvent; use Illuminate\Support\Facades\Mail; use Stackkit\LaravelDatabaseEmails\Email; class SenderTest extends TestCase { - /** @var Swift_Events_SendEvent[] */ + /** @var array */ public $sent = []; public function setUp(): void { parent::setUp(); - if (version_compare(app()->version(), '9.0.0', '>=')) { - Event::listen(MessageSent::class, function (MessageSent $event) { - $this->sent[] = SentMessage::createFromSymfonyMailer( - $event->message->getSymfonySentMessage()->getOriginalMessage() - ); - }); - } else { - Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this)); - } + Event::listen(MessageSent::class, function (MessageSent $event) { + $this->sent[] = SentMessage::createFromSymfonyMailer( + $event->message->getSymfonySentMessage()->getOriginalMessage() + ); + }); } /** @test */ diff --git a/tests/TestingMailEventListener.php b/tests/TestingMailEventListener.php deleted file mode 100644 index 4fa32f1..0000000 --- a/tests/TestingMailEventListener.php +++ /dev/null @@ -1,22 +0,0 @@ -test = $test; - } - - public function beforeSendPerformed(\Swift_Events_Event $event) - { - $this->test->sent[] = SentMessage::createFromSwiftMailer($event->getMessage()); - } -} From 5e5bf3be09e7d20a90ae7f66a434f23c81f3033d Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 13:56:38 +0100 Subject: [PATCH 31/60] Modernize run-test workflow --- .github/workflows/run-tests.yml | 73 ++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e6bb5db..22a5b6b 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,22 +2,72 @@ name: Run tests on: pull_request_target: + types: [opened, synchronize, labeled] schedule: - cron: '0 0 * * *' jobs: - php-tests: - runs-on: ${{ matrix.os }} + access_check: + runs-on: ubuntu-latest + name: Access check + steps: + - name: Ensure pull-request is safe to run + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + if (context.eventName === 'schedule') { + return + } + + // If the user that pushed the commit is a maintainer, skip the check + const collaborators = await github.rest.repos.listCollaborators({ + owner: context.repo.owner, + repo: context.repo.repo + }); + + if (collaborators.data.some(c => c.login === context.actor)) { + console.log(`User ${context.actor} is allowed to run tests because they are a collaborator.`); + return + } + + const issue_number = context.issue.number; + const repository = context.repo.repo; + const owner = context.repo.owner; + + const response = await github.rest.issues.listLabelsOnIssue({ + owner, + repo: repository, + issue_number + }); + const labels = response.data.map(label => label.name); + let hasLabel = labels.includes('safe-to-test') + + if (context.payload.action === 'synchronize' && hasLabel) { + hasLabel = false + await github.rest.issues.removeLabel({ + owner, + repo: repository, + issue_number, + name: 'safe-to-test' + }); + } + + if (!hasLabel) { + throw "Action was not authorized. Exiting now." + } + php-tests: + runs-on: ubuntu-latest + needs: access_check strategy: matrix: - os: [ubuntu-latest] payload: - { laravel: '11.*', php: '8.3', 'testbench': '9.*' } - { laravel: '11.*', php: '8.2', 'testbench': '9.*' } - - { laravel: '10.*', php: '8.3', 'testbench': '8.*'} - - { laravel: '10.*', php: '8.2', 'testbench': '8.*'} - - { laravel: '10.*', php: '8.1', 'testbench': '8.*'} + - { laravel: '10.*', php: '8.3', 'testbench': '8.*' } + - { laravel: '10.*', php: '8.2', 'testbench': '8.*' } + - { laravel: '10.*', php: '8.1', 'testbench': '8.*' } name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} @@ -36,7 +86,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -50,11 +102,4 @@ jobs: composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update composer update --prefer-stable --prefer-dist --no-interaction - name: Execute tests - env: - CI_DB_DRIVER: mysql - CI_DB_HOST: 127.0.0.1 - CI_DB_PORT: 3307 - CI_DB_DATABASE: test - CI_DB_USERNAME: root - CI_DB_PASSWORD: root run: composer test From a01f0c74f76d19941677574d8f68da1c72817e62 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:00:58 +0100 Subject: [PATCH 32/60] Update to new PHPUnit attributes --- phpunit.xml | 12 +++++----- tests/ConfigCacheTest.php | 5 +++-- tests/ConfigTest.php | 11 ++++----- tests/DatabaseInteractionTest.php | 37 ++++++++++++++++--------------- tests/EncryptionTest.php | 17 +++++++------- tests/MailableReaderTest.php | 17 +++++++------- tests/PruneTest.php | 6 ++--- tests/QueuedEmailsTest.php | 17 +++++++------- tests/SendEmailsCommandTest.php | 19 ++++++++-------- tests/SenderTest.php | 25 +++++++++++---------- tests/ValidatorTest.php | 25 +++++++++++---------- 11 files changed, 100 insertions(+), 91 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index eb3301d..a1ec749 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,5 @@ - - - - src/ - - + ./tests/ @@ -19,4 +14,9 @@ + + + src/ + + diff --git a/tests/ConfigCacheTest.php b/tests/ConfigCacheTest.php index 2851e6b..5b1fccc 100644 --- a/tests/ConfigCacheTest.php +++ b/tests/ConfigCacheTest.php @@ -2,18 +2,19 @@ namespace Tests; +use PHPUnit\Framework\Attributes\Test; use Throwable; class ConfigCacheTest extends TestCase { - /** @test */ + #[Test] public function the_configuration_file_can_be_cached() { $failed = false; try { serialize(require __DIR__ . '/../config/laravel-database-emails.php'); - } catch (Throwable $e) { + } catch (Throwable) { $failed = true; } diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index e6c376d..72c5b31 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -2,11 +2,12 @@ namespace Tests; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Config; class ConfigTest extends TestCase { - /** @test */ + #[Test] public function test_max_attempt_count() { $this->assertEquals(3, Config::maxAttemptCount()); @@ -16,7 +17,7 @@ public function test_max_attempt_count() $this->assertEquals(5, Config::maxAttemptCount()); } - /** @test */ + #[Test] public function test_encrypt_emails() { $this->assertFalse(Config::encryptEmails()); @@ -26,7 +27,7 @@ public function test_encrypt_emails() $this->assertTrue(Config::encryptEmails()); } - /** @test */ + #[Test] public function test_testing() { $this->assertFalse(Config::testing()); @@ -36,7 +37,7 @@ public function test_testing() $this->assertTrue(Config::testing()); } - /** @test */ + #[Test] public function test_test_email_address() { $this->assertEquals('test@email.com', Config::testEmailAddress()); @@ -46,7 +47,7 @@ public function test_test_email_address() $this->assertEquals('test+update@email.com', Config::testEmailAddress()); } - /** @test */ + #[Test] public function test_cronjob_email_limit() { $this->assertEquals(20, Config::cronjobEmailLimit()); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 1bc8d71..2eb67fb 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -4,10 +4,11 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; class DatabaseInteractionTest extends TestCase { - /** @test */ + #[Test] public function label_should_be_saved_correctly() { $email = $this->sendEmail(['label' => 'welcome-email']); @@ -16,7 +17,7 @@ public function label_should_be_saved_correctly() $this->assertEquals('welcome-email', $email->getLabel()); } - /** @test */ + #[Test] public function recipient_should_be_saved_correctly() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); @@ -24,7 +25,7 @@ public function recipient_should_be_saved_correctly() $this->assertEquals('john@doe.com', $email->getRecipient()); } - /** @test */ + #[Test] public function cc_and_bcc_should_be_saved_correctly() { $email = $this->sendEmail([ @@ -44,7 +45,7 @@ public function cc_and_bcc_should_be_saved_correctly() $this->assertEquals(['jane@doe.com'], $email->getBcc()); } - /** @test */ + #[Test] public function reply_to_should_be_saved_correctly() { $email = $this->sendEmail([ @@ -58,7 +59,7 @@ public function reply_to_should_be_saved_correctly() $this->assertEquals(['john@doe.com'], $email->getReplyTo()); } - /** @test */ + #[Test] public function subject_should_be_saved_correclty() { $email = $this->sendEmail(['subject' => 'test subject']); @@ -67,7 +68,7 @@ public function subject_should_be_saved_correclty() $this->assertEquals('test subject', $email->getSubject()); } - /** @test */ + #[Test] public function view_should_be_saved_correctly() { $email = $this->sendEmail(['view' => 'tests::dummy']); @@ -76,7 +77,7 @@ public function view_should_be_saved_correctly() $this->assertEquals('tests::dummy', $email->getView()); } - /** @test */ + #[Test] public function encrypted_should_be_saved_correctly() { $email = $this->sendEmail(); @@ -92,7 +93,7 @@ public function encrypted_should_be_saved_correctly() $this->assertTrue($email->isEncrypted()); } - /** @test */ + #[Test] public function scheduled_date_should_be_saved_correctly() { $email = $this->sendEmail(); @@ -105,7 +106,7 @@ public function scheduled_date_should_be_saved_correctly() $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate()); } - /** @test */ + #[Test] public function the_body_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -116,7 +117,7 @@ public function the_body_should_be_saved_correctly() $this->assertSame($expectedBody, $email->getBody()); } - /** @test */ + #[Test] public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); @@ -132,7 +133,7 @@ public function from_should_be_saved_correctly() $this->assertEquals('Marick', $email->getFromName()); } - /** @test */ + #[Test] public function variables_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); @@ -141,7 +142,7 @@ public function variables_should_be_saved_correctly() $this->assertEquals(['name' => 'John Doe'], $email->getVariables()); } - /** @test */ + #[Test] public function the_sent_date_should_be_null() { $email = $this->sendEmail(); @@ -150,7 +151,7 @@ public function the_sent_date_should_be_null() $this->assertNull($email->getSendDate()); } - /** @test */ + #[Test] public function failed_should_be_zero() { $email = $this->sendEmail(); @@ -159,7 +160,7 @@ public function failed_should_be_zero() $this->assertFalse($email->hasFailed()); } - /** @test */ + #[Test] public function attempts_should_be_zero() { $email = $this->sendEmail(); @@ -168,7 +169,7 @@ public function attempts_should_be_zero() $this->assertEquals(0, $email->getAttempts()); } - /** @test */ + #[Test] public function the_scheduled_date_should_be_saved_correctly() { Carbon::setTestNow(Carbon::now()); @@ -181,7 +182,7 @@ public function the_scheduled_date_should_be_saved_correctly() $this->assertEquals($scheduledFor, $email->getScheduledDate()); } - /** @test */ + #[Test] public function recipient_should_be_swapped_for_test_address_when_in_testing_mode() { $this->app['config']->set('laravel-database-emails.testing.enabled', function () { @@ -194,7 +195,7 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod $this->assertEquals('test@address.com', $email->getRecipient()); } - /** @test */ + #[Test] public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() @@ -219,7 +220,7 @@ public function attachments_should_be_saved_correctly() $this->assertEquals(__DIR__ . '/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); } - /** @test */ + #[Test] public function in_memory_attachments_should_be_saved_correctly() { $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index 36a9328..e835512 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -3,6 +3,7 @@ namespace Tests; use Illuminate\Mail\Mailables\Address; +use PHPUnit\Framework\Attributes\Test; class EncryptionTest extends TestCase { @@ -15,7 +16,7 @@ public function setUp(): void $this->sendEmail(); } - /** @test */ + #[Test] public function an_email_should_be_marked_as_encrypted() { $email = $this->sendEmail(); @@ -23,7 +24,7 @@ public function an_email_should_be_marked_as_encrypted() $this->assertTrue($email->isEncrypted()); } - /** @test */ + #[Test] public function the_recipient_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); @@ -33,7 +34,7 @@ public function the_recipient_should_be_encrypted_and_decrypted() $this->assertEquals('john@doe.com', $email->getRecipient()); } - /** @test */ + #[Test] public function cc_and_bb_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ @@ -48,7 +49,7 @@ public function cc_and_bb_should_be_encrypted_and_decrypted() $this->assertEquals($bcc, $email->getBcc()); } - /** @test */ + #[Test] public function reply_to_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ @@ -79,7 +80,7 @@ public function reply_to_should_be_encrypted_and_decrypted() $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo()); } - /** @test */ + #[Test] public function the_subject_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['subject' => 'test subject']); @@ -89,7 +90,7 @@ public function the_subject_should_be_encrypted_and_decrypted() $this->assertEquals('test subject', $email->getSubject()); } - /** @test */ + #[Test] public function the_variables_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -105,7 +106,7 @@ public function the_variables_should_be_encrypted_and_decrypted() ); } - /** @test */ + #[Test] public function the_body_should_be_encrypted_and_decrypted() { $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); @@ -117,7 +118,7 @@ public function the_body_should_be_encrypted_and_decrypted() $this->assertEquals($expectedBody, $email->getBody()); } - /** @test */ + #[Test] public function from_should_be_encrypted_and_decrypted() { $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 1fc6acb..6691d8e 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -7,6 +7,7 @@ use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class MailableReaderTest extends TestCase @@ -16,7 +17,7 @@ private function mailable(): Mailable return new TestMailable(); } - /** @test */ + #[Test] public function it_extracts_the_recipient() { $composer = Email::compose() @@ -34,7 +35,7 @@ public function it_extracts_the_recipient() $this->assertContains('jane@doe.com', $composer->getData('recipient')); } - /** @test */ + #[Test] public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -42,7 +43,7 @@ public function it_extracts_cc_addresses() $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc')); } - /** @test */ + #[Test] public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -50,7 +51,7 @@ public function it_extracts_bcc_addresses() $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); } - /** @test */ + #[Test] public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); @@ -58,7 +59,7 @@ public function it_extracts_reply_to_addresses() $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to')); } - /** @test */ + #[Test] public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); @@ -66,7 +67,7 @@ public function it_extracts_the_subject() $this->assertEquals('Your order has shipped!', $composer->getData('subject')); } - /** @test */ + #[Test] public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); @@ -74,7 +75,7 @@ public function it_extracts_the_body() $this->assertEquals("Name: John Doe\n", $composer->getData('body')); } - /** @test */ + #[Test] public function it_extracts_attachments() { $email = Email::compose()->mailable($this->mailable())->send(); @@ -91,7 +92,7 @@ public function it_extracts_attachments() $this->assertEquals('

Thanks for your oder

', $attachments[1]['attachment']['data']); } - /** @test */ + #[Test] public function it_extracts_the_from_address_and_or_name() { $email = Email::compose()->mailable( diff --git a/tests/PruneTest.php b/tests/PruneTest.php index 03a51cc..ddcf8f1 100644 --- a/tests/PruneTest.php +++ b/tests/PruneTest.php @@ -2,13 +2,13 @@ namespace Tests; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class PruneTest extends TestCase { - /** @test */ + #[Test] public function by_default_mails_are_pruned_after_6_months() { $email = $this->sendEmail(); @@ -29,7 +29,7 @@ public function by_default_mails_are_pruned_after_6_months() $this->assertNull($email->fresh()); } - /** @test */ + #[Test] public function can_change_when_emails_are_pruned() { Email::pruneWhen(function (Email $email) { diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 0bb7b96..a0b277d 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -3,6 +3,7 @@ namespace Tests; use Illuminate\Support\Facades\Queue; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; use Illuminate\Support\Facades\Mail; @@ -13,7 +14,7 @@ public function setUp(): void parent::setUp(); } - /** @test */ + #[Test] public function queueing_an_email_will_leave_sending_on_false() { $email = $this->queueEmail(); @@ -21,7 +22,7 @@ public function queueing_an_email_will_leave_sending_on_false() $this->assertEquals(0, $email->sending); } - /** @test */ + #[Test] public function queueing_an_email_will_set_the_queued_at_column() { $email = $this->queueEmail(); @@ -29,7 +30,7 @@ public function queueing_an_email_will_set_the_queued_at_column() $this->assertNotNull($email->queued_at); } - /** @test */ + #[Test] public function queueing_an_email_will_dispatch_a_job() { Queue::fake(); @@ -41,7 +42,7 @@ public function queueing_an_email_will_dispatch_a_job() }); } - /** @test */ + #[Test] public function emails_can_be_queued_on_a_specific_connection() { Queue::fake(); @@ -53,7 +54,7 @@ public function emails_can_be_queued_on_a_specific_connection() }); } - /** @test */ + #[Test] public function emails_can_be_queued_on_a_specific_queue() { Queue::fake(); @@ -65,7 +66,7 @@ public function emails_can_be_queued_on_a_specific_queue() }); } - /** @test */ + #[Test] public function emails_can_be_queued_with_a_delay() { Queue::fake(); @@ -79,7 +80,7 @@ public function emails_can_be_queued_with_a_delay() }); } - /** @test */ + #[Test] public function the_send_email_job_will_call_send_on_the_email_instance() { Queue::fake(); @@ -93,7 +94,7 @@ public function the_send_email_job_will_call_send_on_the_email_instance() $job->handle(); } - /** @test */ + #[Test] public function the_mail_will_be_marked_as_sent_when_job_is_finished() { Queue::fake(); diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index ccd38d7..00b52f1 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -5,11 +5,12 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Queue; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Store; class SendEmailsCommandTest extends TestCase { - /** @test */ + #[Test] public function an_email_should_be_marked_as_sent() { $email = $this->sendEmail(); @@ -19,7 +20,7 @@ public function an_email_should_be_marked_as_sent() $this->assertNotNull($email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function the_number_of_attempts_should_be_incremented() { $email = $this->sendEmail(); @@ -31,7 +32,7 @@ public function the_number_of_attempts_should_be_incremented() $this->assertEquals(1, $email->fresh()->getAttempts()); } - /** @test */ + #[Test] public function an_email_should_not_be_sent_once_it_is_marked_as_sent() { $email = $this->sendEmail(); @@ -46,7 +47,7 @@ public function an_email_should_not_be_sent_once_it_is_marked_as_sent() $this->assertEquals($firstSend, $email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function an_email_should_not_be_sent_if_it_is_queued() { Queue::fake(); @@ -58,7 +59,7 @@ public function an_email_should_not_be_sent_if_it_is_queued() $this->assertNull($email->fresh()->getSendDate()); } - /** @test */ + #[Test] public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database() { $email = $this->sendEmail(); @@ -71,7 +72,7 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database $this->assertStringContainsString('RfcComplianceException', $email->fresh()->getError()); } - /** @test */ + #[Test] public function the_number_of_emails_sent_per_minute_should_be_limited() { for ($i = 1; $i <= 30; $i++) { @@ -85,7 +86,7 @@ public function the_number_of_emails_sent_per_minute_should_be_limited() $this->assertEquals(5, DB::table('emails')->whereNull('sent_at')->count()); } - /** @test */ + #[Test] public function an_email_should_never_be_sent_before_its_scheduled_date() { $email = $this->scheduleEmail(Carbon::now()->addHour(1)); @@ -101,7 +102,7 @@ public function an_email_should_never_be_sent_before_its_scheduled_date() $this->assertNotNull($email->getSendDate()); } - /** @test */ + #[Test] public function emails_will_be_sent_until_max_try_count_has_been_reached() { $this->app['config']['mail.driver'] = 'does-not-exist'; @@ -116,7 +117,7 @@ public function emails_will_be_sent_until_max_try_count_has_been_reached() $this->assertCount(0, (new Store)->getQueue()); } - /** @test */ + #[Test] public function the_failed_status_and_error_is_cleared_if_a_previously_failed_email_is_sent_succesfully() { $email = $this->sendEmail(); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 1745d45..b6a6907 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -4,6 +4,7 @@ use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Event; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; use Illuminate\Support\Facades\Mail; @@ -25,7 +26,7 @@ public function setUp(): void }); } - /** @test */ + #[Test] public function it_sends_an_email() { $this->sendEmail(); @@ -35,7 +36,7 @@ public function it_sends_an_email() $this->artisan('email:send'); } - /** @test */ + #[Test] public function the_email_has_a_correct_from_email_and_from_name() { $this->app['config']->set('mail.from.address', 'testfromaddress@gmail.com'); @@ -76,7 +77,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->assertEquals('Marick', $from[key($from)]); } - /** @test */ + #[Test] public function it_sends_emails_to_the_correct_recipients() { $this->sendEmail(['recipient' => 'john@doe.com']); @@ -94,7 +95,7 @@ public function it_sends_emails_to_the_correct_recipients() $this->assertArrayHasKey('john+2@doe.com', $to); } - /** @test */ + #[Test] public function it_adds_the_cc_addresses() { $this->sendEmail(['cc' => 'cc@test.com']); @@ -112,7 +113,7 @@ public function it_adds_the_cc_addresses() $this->assertArrayHasKey('cc+2@test.com', $cc); } - /** @test */ + #[Test] public function it_adds_the_bcc_addresses() { $this->sendEmail(['bcc' => 'bcc@test.com']); @@ -130,7 +131,7 @@ public function it_adds_the_bcc_addresses() $this->assertArrayHasKey('bcc+2@test.com', $bcc); } - /** @test */ + #[Test] public function the_email_has_the_correct_subject() { $this->sendEmail(['subject' => 'Hello World']); @@ -142,7 +143,7 @@ public function the_email_has_the_correct_subject() $this->assertEquals('Hello World', $subject); } - /** @test */ + #[Test] public function the_email_has_the_correct_body() { $this->sendEmail(['variables' => ['name' => 'John Doe']]); @@ -157,7 +158,7 @@ public function the_email_has_the_correct_body() $this->assertEquals(view('tests::dummy'), $body); } - /** @test */ + #[Test] public function attachments_are_added_to_the_email() { $this->composeEmail() @@ -170,7 +171,7 @@ public function attachments_are_added_to_the_email() $this->assertCount(1, $attachments); } - /** @test */ + #[Test] public function raw_attachments_are_added_to_the_email() { $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); @@ -191,7 +192,7 @@ public function raw_attachments_are_added_to_the_email() $this->assertTrue(md5($attachment['body']) == md5($rawData)); } - /** @test */ + #[Test] public function old_json_encoded_attachments_can_still_be_read() { $email = $this->sendEmail(); @@ -206,7 +207,7 @@ public function old_json_encoded_attachments_can_still_be_read() $this->assertEquals([4, 5, 6], $email->fresh()->getAttachments()); } - /** @test */ + #[Test] public function emails_can_be_sent_immediately() { $this->app['config']->set('laravel-database-emails.immediately', false); @@ -222,7 +223,7 @@ public function emails_can_be_sent_immediately() $this->assertCount(1, $this->sent); } - /** @test */ + #[Test] public function it_adds_the_reply_to_addresses() { $this->sendEmail(['reply_to' => 'replyto@test.com']); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index b8b02ce..45ff991 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -4,11 +4,12 @@ use Carbon; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; class ValidatorTest extends TestCase { - /** @test */ + #[Test] public function a_label_cannot_contain_more_than_255_characters() { $this->expectException(InvalidArgumentException::class); @@ -18,7 +19,7 @@ public function a_label_cannot_contain_more_than_255_characters() ->send(); } - /** @test */ + #[Test] public function a_recipient_is_required() { $this->expectException(InvalidArgumentException::class); @@ -28,7 +29,7 @@ public function a_recipient_is_required() ->send(); } - /** @test */ + #[Test] public function a_recipient_cannot_be_empty() { $this->expectException(InvalidArgumentException::class); @@ -39,7 +40,7 @@ public function a_recipient_cannot_be_empty() ->send(); } - /** @test */ + #[Test] public function the_recipient_email_must_be_valid() { $this->expectException(InvalidArgumentException::class); @@ -50,7 +51,7 @@ public function the_recipient_email_must_be_valid() ->send(); } - /** @test */ + #[Test] public function cc_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -65,7 +66,7 @@ public function cc_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function bcc_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -80,7 +81,7 @@ public function bcc_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function reply_to_must_contain_valid_email_addresses() { $this->expectException(InvalidArgumentException::class); @@ -95,7 +96,7 @@ public function reply_to_must_contain_valid_email_addresses() ->send(); } - /** @test */ + #[Test] public function a_subject_is_required() { $this->expectException(InvalidArgumentException::class); @@ -106,7 +107,7 @@ public function a_subject_is_required() ->send(); } - /** @test */ + #[Test] public function a_view_is_required() { $this->expectException(InvalidArgumentException::class); @@ -118,7 +119,7 @@ public function a_view_is_required() ->send(); } - /** @test */ + #[Test] public function the_view_must_exist() { // this view exists, if error thrown -> fail test @@ -142,7 +143,7 @@ public function the_view_must_exist() ->send(); } - /** @test */ + #[Test] public function variables_must_be_defined_as_an_array() { $email = Email::compose() @@ -168,7 +169,7 @@ public function variables_must_be_defined_as_an_array() } } - /** @test */ + #[Test] public function the_scheduled_date_must_be_a_carbon_instance_or_a_valid_date() { // invalid From bc9c2c257e9ed140b9f5129aec8300ec1d4d9ae5 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:49:16 +0100 Subject: [PATCH 33/60] wip --- .github/workflows/run-tests.yml | 35 ++++++++++----- composer.json | 30 ++++++++++--- docker-compose.yml | 18 ++++++++ testbench.yaml | 23 ++++++++++ tests/QueuedEmailsTest.php | 9 ++-- tests/TestCase.php | 44 +++++++++++-------- workbench/app/Models/.gitkeep | 0 .../Providers/WorkbenchServiceProvider.php | 24 ++++++++++ workbench/bootstrap/.gitkeep | 0 workbench/bootstrap/app.php | 19 ++++++++ workbench/bootstrap/providers.php | 5 +++ workbench/database/factories/.gitkeep | 0 workbench/database/migrations/.gitkeep | 0 workbench/database/seeders/.gitkeep | 0 workbench/database/seeders/DatabaseSeeder.php | 17 +++++++ workbench/resources/views/.gitkeep | 0 workbench/routes/.gitkeep | 0 workbench/routes/console.php | 8 ++++ workbench/routes/web.php | 7 +++ 19 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 docker-compose.yml create mode 100644 testbench.yaml create mode 100644 workbench/app/Models/.gitkeep create mode 100644 workbench/app/Providers/WorkbenchServiceProvider.php create mode 100644 workbench/bootstrap/.gitkeep create mode 100644 workbench/bootstrap/app.php create mode 100644 workbench/bootstrap/providers.php create mode 100644 workbench/database/factories/.gitkeep create mode 100644 workbench/database/migrations/.gitkeep create mode 100644 workbench/database/seeders/.gitkeep create mode 100644 workbench/database/seeders/DatabaseSeeder.php create mode 100644 workbench/resources/views/.gitkeep create mode 100644 workbench/routes/.gitkeep create mode 100644 workbench/routes/console.php create mode 100644 workbench/routes/web.php diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 22a5b6b..39e170d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,23 +62,22 @@ jobs: needs: access_check strategy: matrix: + db: [ 'mysql', 'sqlite' ] payload: - - { laravel: '11.*', php: '8.3', 'testbench': '9.*' } - - { laravel: '11.*', php: '8.2', 'testbench': '9.*' } - - { laravel: '10.*', php: '8.3', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.2', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.1', 'testbench': '8.*' } + - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } + - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } + - { laravel: '10.*', php: '8.3', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.2', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.1', 'testbench': '8.*', collision: '7.*' } - name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} + name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} services: mysql: image: mysql:8 env: - MYSQL_USER: root - MYSQL_ROOT_PASSWORD: root - MYSQL_PASSWORD: - MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_USER: test + MYSQL_PASSWORD: test MYSQL_DATABASE: test ports: - 3307:3306 @@ -97,9 +96,23 @@ jobs: extensions: mbstring, dom, fileinfo, mysql coverage: none + - name: Set up MySQL and PostgreSQL + run: | + MYSQL_PORT=3307 POSTGRES_PORT=5432 docker compose up ${{ matrix.db }} -d + - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" "nunomaduro/collision:${{ matrix.payload.collision }}" --no-interaction --no-update composer update --prefer-stable --prefer-dist --no-interaction + if [ "${{ matrix.db }}" = "mysql" ]; then + while ! mysqladmin ping --host=127.0.0.1 --user=test --port=3307 --password=test --silent; do + echo "Waiting for MySQL..." + sleep 1 + done + else + echo "Not waiting for MySQL." + fi - name: Execute tests + env: + DB_DRIVER: ${{ matrix.db }} run: composer test diff --git a/composer.json b/composer.json index ed7846f..c3df63c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,10 @@ }, "autoload-dev": { "psr-4": { - "Tests\\": "tests/" + "Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" } }, "extra": { @@ -32,19 +35,36 @@ "require-dev": { "mockery/mockery": "^1.2", "orchestra/testbench": "^8.0|^9.0", + "nunomaduro/collision": "^7.0|^8.0", "laravel/pint": "^1.14" }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { "l11": [ - "composer update laravel/framework:11.* orchestra/testbench:9.* --with-all-dependencies" + "composer update laravel/framework:11.* orchestra/testbench:9.* nunomaduro/collision:8.* --with-all-dependencies" ], "l10": [ - "composer update laravel/framework:10.* orchestra/testbench:8.* --with-all-dependencies" + "composer update laravel/framework:10.* orchestra/testbench:8.* nunomaduro/collision:7.* --with-all-dependencies" ], "test": [ - "CI_DB_DRIVER=sqlite CI_DB_DATABASE=:memory: phpunit" + "testbench package:test" + ], + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve" + ], + "lint": [ + "@php vendor/bin/pint", + "@php vendor/bin/phpstan analyse" ] } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..972092b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + mysql: + image: mysql:8 + ports: + - '${MYSQL_PORT:-3307}:3306' + environment: + MYSQL_USER: 'test' + MYSQL_PASSWORD: 'test' + MYSQL_DATABASE: 'test' + MYSQL_RANDOM_ROOT_PASSWORD: true + pgsql: + image: postgres:14 + ports: + - '${POSTGRES_PORT:-5432}:5432' + environment: + POSTGRES_USER: 'test' + POSTGRES_PASSWORD: 'test' + POSTGRES_DB: 'test' diff --git a/testbench.yaml b/testbench.yaml new file mode 100644 index 0000000..707fae0 --- /dev/null +++ b/testbench.yaml @@ -0,0 +1,23 @@ +providers: + - Workbench\App\Providers\WorkbenchServiceProvider + - Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider + +migrations: + - workbench/database/migrations + +seeders: + - Workbench\Database\Seeders\DatabaseSeeder + +workbench: + start: '/' + install: true + health: false + discovers: + web: true + api: false + commands: false + components: false + views: false + build: [] + assets: [] + sync: [] diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index a0b277d..560fee1 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -9,14 +9,11 @@ class QueuedEmailsTest extends TestCase { - public function setUp(): void - { - parent::setUp(); - } - #[Test] public function queueing_an_email_will_leave_sending_on_false() { + Queue::fake(); + $email = $this->queueEmail(); $this->assertEquals(0, $email->sending); @@ -25,6 +22,8 @@ public function queueing_an_email_will_leave_sending_on_false() #[Test] public function queueing_an_email_will_set_the_queued_at_column() { + Queue::fake(); + $email = $this->queueEmail(); $this->assertNotNull($email->queued_at); diff --git a/tests/TestCase.php b/tests/TestCase.php index 482aded..7e3a78d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,12 +2,18 @@ namespace Tests; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Foundation\Testing\RefreshDatabase; use Stackkit\LaravelDatabaseEmails\Email; +use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; class TestCase extends \Orchestra\Testbench\TestCase { protected $invalid; + use LazilyRefreshDatabase; + public function setUp(): void { parent::setUp(); @@ -31,20 +37,10 @@ function () { Email::truncate(); } - /** - * Get package providers. At a minimum this is the package being tested, but also - * would include packages upon which our package depends, e.g. Cartalyst/Sentry - * In a normal app environment these would be added to the 'providers' array in - * the config/app.php file. - * - * @param \Illuminate\Foundation\Application $app - * - * @return array - */ protected function getPackageProviders($app) { return [ - \Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider::class, + LaravelDatabaseEmailsServiceProvider::class, ]; } @@ -61,15 +57,25 @@ protected function getEnvironmentSetUp($app) $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); $app['config']->set('database.default', 'testbench'); + $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ - 'driver' => getenv('CI_DB_DRIVER'), - 'host' => getenv('CI_DB_HOST'), - 'port' => getenv('CI_DB_PORT'), - 'database' => getenv('CI_DB_DATABASE'), - 'username' => getenv('CI_DB_USERNAME'), - 'password' => getenv('CI_DB_PASSWORD'), - 'prefix' => '', - 'strict' => true, + 'driver' => $driver, + ...match($driver) { + 'sqlite' => [ + 'database' => ':memory:', + ], + 'mysql' => [ + 'host' => '127.0.0.1', + 'port' => 3307, + ], + 'pgsql' => [ + 'host' => '127.0.0.1', + 'port' => 5432, + ], + }, + 'database' => 'test', + 'username' => 'test', + 'password' => 'test', ]); $app['config']->set('mail.driver', 'log'); diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 0000000..e8cec9c --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->create(); diff --git a/workbench/bootstrap/providers.php b/workbench/bootstrap/providers.php new file mode 100644 index 0000000..3ac44ad --- /dev/null +++ b/workbench/bootstrap/providers.php @@ -0,0 +1,5 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/workbench/routes/web.php b/workbench/routes/web.php new file mode 100644 index 0000000..86a06c5 --- /dev/null +++ b/workbench/routes/web.php @@ -0,0 +1,7 @@ + Date: Sat, 16 Mar 2024 14:49:59 +0100 Subject: [PATCH 34/60] Pint --- .../2017_12_14_151403_create_emails_table.php | 4 +- ...151421_add_attachments_to_emails_table.php | 4 +- ..._12_22_114011_add_from_to_emails_table.php | 4 +- ...02_21000_add_queued_at_to_emails_table.php | 4 +- ...28_140000_add_reply_to_to_emails_table.php | 4 +- src/Config.php | 12 -- src/Email.php | 103 +++--------------- src/EmailComposer.php | 67 ++---------- src/Encrypter.php | 20 +--- src/HasEncryptedAttributes.php | 2 +- src/LaravelDatabaseEmailsServiceProvider.php | 18 +-- src/MailableReader.php | 22 +--- src/Preparer.php | 35 +----- src/SendEmailsCommand.php | 7 -- src/Sender.php | 9 +- src/SentMessage.php | 8 ++ src/Validator.php | 29 ++--- tests/ConfigCacheTest.php | 2 +- tests/DatabaseInteractionTest.php | 16 +-- tests/EncryptionTest.php | 10 +- tests/MailableReaderTest.php | 8 +- tests/PruneTest.php | 8 +- tests/QueuedEmailsTest.php | 2 +- tests/SendEmailsCommandTest.php | 4 +- tests/SenderTest.php | 8 +- tests/TestCase.php | 22 ++-- workbench/database/seeders/DatabaseSeeder.php | 1 - 27 files changed, 104 insertions(+), 329 deletions(-) diff --git a/database/migrations/2017_12_14_151403_create_emails_table.php b/database/migrations/2017_12_14_151403_create_emails_table.php index dc49d1f..23489da 100644 --- a/database/migrations/2017_12_14_151403_create_emails_table.php +++ b/database/migrations/2017_12_14_151403_create_emails_table.php @@ -1,8 +1,8 @@ update([ 'attempts' => $this->attempts + 1, - 'sending' => 1, + 'sending' => 1, ]); } /** * Mark the e-mail as sent. - * - * @return void */ public function markAsSent(): void { @@ -490,30 +424,25 @@ public function markAsSent(): void $this->update([ 'sending' => 0, 'sent_at' => $now, - 'failed' => 0, - 'error' => '', + 'failed' => 0, + 'error' => '', ]); } /** * Mark the e-mail as failed. - * - * @param Exception $exception - * @return void */ public function markAsFailed(Exception $exception): void { $this->update([ 'sending' => 0, - 'failed' => 1, - 'error' => (string) $exception, + 'failed' => 1, + 'error' => (string) $exception, ]); } /** * Send the e-mail. - * - * @return void */ public function send(): void { @@ -522,8 +451,6 @@ public function send(): void /** * Retry sending the e-mail. - * - * @return void */ public function retry(): void { @@ -531,12 +458,12 @@ public function retry(): void $retry->fill( [ - 'id' => null, - 'attempts' => 0, - 'sending' => 0, - 'failed' => 0, - 'error' => null, - 'sent_at' => null, + 'id' => null, + 'attempts' => 0, + 'sending' => 0, + 'failed' => 0, + 'error' => null, + 'sent_at' => null, 'delivered_at' => null, ] ); @@ -545,11 +472,10 @@ public function retry(): void } /** - * @param string $key - * @param mixed $default + * @param mixed $default * @return mixed */ - public function getRawDatabaseValue(string $key = null, $default = null) + public function getRawDatabaseValue(?string $key = null, $default = null) { if (method_exists($this, 'getRawOriginal')) { return $this->getRawOriginal($key, $default); @@ -559,7 +485,6 @@ public function getRawDatabaseValue(string $key = null, $default = null) } /** - * @param Closure $closure * @return void */ public static function pruneWhen(Closure $closure) diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 352e63e..905d0a1 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -24,8 +24,6 @@ class EmailComposer /** * Create a new EmailComposer instance. - * - * @param Email $email */ public function __construct(Email $email) { @@ -34,8 +32,6 @@ public function __construct(Email $email) /** * Get the e-mail that is being composed. - * - * @return Email */ public function getEmail(): Email { @@ -45,9 +41,7 @@ public function getEmail(): Email /** * Set a data value. * - * @param string $key - * @param mixed $value - * @return self + * @param mixed $value */ public function setData(string $key, $value): self { @@ -59,8 +53,7 @@ public function setData(string $key, $value): self /** * Get a data value. * - * @param string $key - * @param mixed $default + * @param mixed $default * @return mixed */ public function getData(string $key, $default = null) @@ -74,9 +67,6 @@ public function getData(string $key, $default = null) /** * Determine if the given data value was set. - * - * @param string $key - * @return bool */ public function hasData(string $key): bool { @@ -85,9 +75,6 @@ public function hasData(string $key): bool /** * Set the e-mail label. - * - * @param string $label - * @return self */ public function label(string $label): self { @@ -96,10 +83,6 @@ public function label(string $label): self /** * Set the e-mail from address and aname. - * - * @param string|null $address - * @param string|null $name - * @return self */ public function from(?string $address = null, ?string $name = null): self { @@ -109,8 +92,7 @@ public function from(?string $address = null, ?string $name = null): self /** * Set the e-mail recipient(s). * - * @param string|array $recipient - * @return self + * @param string|array $recipient */ public function recipient($recipient): self { @@ -120,8 +102,7 @@ public function recipient($recipient): self /** * Define the carbon-copy address(es). * - * @param string|array $cc - * @return self + * @param string|array $cc */ public function cc($cc): self { @@ -131,8 +112,7 @@ public function cc($cc): self /** * Define the blind carbon-copy address(es). * - * @param string|array $bcc - * @return self + * @param string|array $bcc */ public function bcc($bcc): self { @@ -142,8 +122,7 @@ public function bcc($bcc): self /** * Define the reply-to address(es). * - * @param string|array $replyTo - * @return self + * @param string|array $replyTo */ public function replyTo($replyTo): self { @@ -152,9 +131,6 @@ public function replyTo($replyTo): self /** * Set the e-mail subject. - * - * @param string $subject - * @return self */ public function subject(string $subject): self { @@ -163,9 +139,6 @@ public function subject(string $subject): self /** * Set the e-mail view. - * - * @param string $view - * @return self */ public function view(string $view): self { @@ -174,9 +147,6 @@ public function view(string $view): self /** * Set the e-mail variables. - * - * @param array $variables - * @return self */ public function variables(array $variables): self { @@ -186,8 +156,7 @@ public function variables(array $variables): self /** * Schedule the e-mail. * - * @param mixed $scheduledAt - * @return Email + * @param mixed $scheduledAt */ public function schedule($scheduledAt): Email { @@ -197,8 +166,7 @@ public function schedule($scheduledAt): Email /** * Schedule the e-mail. * - * @param mixed $scheduledAt - * @return Email + * @param mixed $scheduledAt */ public function later($scheduledAt): Email { @@ -210,10 +178,7 @@ public function later($scheduledAt): Email /** * Queue the e-mail. * - * @param string|null $connection - * @param string|null $queue - * @param \DateTimeInterface|\DateInterval|int|null $delay - * @return Email + * @param \DateTimeInterface|\DateInterval|int|null $delay */ public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email { @@ -230,9 +195,6 @@ public function queue(?string $connection = null, ?string $queue = null, $delay /** * Set the Mailable. - * - * @param Mailable $mailable - * @return self */ public function mailable(Mailable $mailable): self { @@ -245,10 +207,6 @@ public function mailable(Mailable $mailable): self /** * Attach a file to the e-mail. - * - * @param string $file - * @param array $options - * @return self */ public function attach(string $file, array $options = []): self { @@ -261,11 +219,6 @@ public function attach(string $file, array $options = []): self /** * Attach in-memory data as an attachment. - * - * @param string $data - * @param string $name - * @param array $options - * @return self */ public function attachData(string $data, string $name, array $options = []): self { @@ -278,8 +231,6 @@ public function attachData(string $data, string $name, array $options = []): sel /** * Send the e-mail. - * - * @return Email */ public function send(): Email { diff --git a/src/Encrypter.php b/src/Encrypter.php index 14052a7..7045c2c 100644 --- a/src/Encrypter.php +++ b/src/Encrypter.php @@ -8,8 +8,6 @@ class Encrypter { /** * Encrypt the given e-mail. - * - * @param EmailComposer $composer */ public function encrypt(EmailComposer $composer): void { @@ -30,8 +28,6 @@ public function encrypt(EmailComposer $composer): void /** * Mark the e-mail as encrypted. - * - * @param EmailComposer $composer */ private function setEncrypted(EmailComposer $composer): void { @@ -40,8 +36,6 @@ private function setEncrypted(EmailComposer $composer): void /** * Encrypt the e-mail reply-to. - * - * @param EmailComposer $composer */ private function encryptReplyTo(EmailComposer $composer): void { @@ -54,8 +48,6 @@ private function encryptReplyTo(EmailComposer $composer): void /** * Encrypt the e-mail addresses of the recipients. - * - * @param EmailComposer $composer */ private function encryptRecipients(EmailComposer $composer): void { @@ -63,15 +55,13 @@ private function encryptRecipients(EmailComposer $composer): void $email->fill([ 'recipient' => encrypt($email->recipient), - 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', - 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', + 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', + 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', ]); } /** * Encrypt the e-mail addresses for the from field. - * - * @param EmailComposer $composer */ private function encryptFrom(EmailComposer $composer): void { @@ -84,8 +74,6 @@ private function encryptFrom(EmailComposer $composer): void /** * Encrypt the e-mail subject. - * - * @param EmailComposer $composer */ private function encryptSubject(EmailComposer $composer): void { @@ -98,8 +86,6 @@ private function encryptSubject(EmailComposer $composer): void /** * Encrypt the e-mail variables. - * - * @param EmailComposer $composer */ private function encryptVariables(EmailComposer $composer): void { @@ -116,8 +102,6 @@ private function encryptVariables(EmailComposer $composer): void /** * Encrypt the e-mail body. - * - * @param EmailComposer $composer */ private function encryptBody(EmailComposer $composer): void { diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index 01dfbb4..7d02012 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -41,7 +41,7 @@ trait HasEncryptedAttributes /** * Get an attribute from the model. * - * @param string $key + * @param string $key * @return mixed */ public function getAttribute($key) diff --git a/src/LaravelDatabaseEmailsServiceProvider.php b/src/LaravelDatabaseEmailsServiceProvider.php index 57e249b..a7b898c 100644 --- a/src/LaravelDatabaseEmailsServiceProvider.php +++ b/src/LaravelDatabaseEmailsServiceProvider.php @@ -10,8 +10,6 @@ class LaravelDatabaseEmailsServiceProvider extends ServiceProvider { /** * Perform post-registration booting of services. - * - * @return void */ public function boot(): void { @@ -21,28 +19,24 @@ public function boot(): void /** * Boot the config for the package. - * - * @return void */ private function bootConfig(): void { - $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; - $configDir = $baseDir . 'config' . DIRECTORY_SEPARATOR; + $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; + $configDir = $baseDir.'config'.DIRECTORY_SEPARATOR; $this->publishes([ - $configDir . 'laravel-database-emails.php' => config_path('laravel-database-emails.php'), + $configDir.'laravel-database-emails.php' => config_path('laravel-database-emails.php'), ], 'laravel-database-emails-config'); } /** * Boot the database for the package. - * - * @return void */ private function bootDatabase(): void { - $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; - $migrationsDir = $baseDir . 'database' . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR; + $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; + $migrationsDir = $baseDir.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR; if ($this->app['config']->get('laravel-database-emails.manual_migrations')) { $this->publishes([ @@ -55,8 +49,6 @@ private function bootDatabase(): void /** * Register the service provider. - * - * @return void */ public function register(): void { diff --git a/src/MailableReader.php b/src/MailableReader.php index 86be654..22a2238 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -12,8 +12,6 @@ class MailableReader { /** * Read the mailable and pass the data to the email composer. - * - * @param EmailComposer $composer */ public function read(EmailComposer $composer): void { @@ -46,8 +44,7 @@ public function read(EmailComposer $composer): void /** * Convert the mailable addresses array into a array with only e-mails. * - * @param string $from - * @return array + * @param string $from */ private function convertMailableAddresses($from): array { @@ -58,8 +55,6 @@ private function convertMailableAddresses($from): array /** * Read the mailable recipient to the email composer. - * - * @param EmailComposer $composer */ private function readRecipient(EmailComposer $composer): void { @@ -72,14 +67,12 @@ private function readRecipient(EmailComposer $composer): void /** * Read the mailable from field to the email composer. - * - * @param EmailComposer $composer */ private function readFrom(EmailComposer $composer): void { $from = reset($composer->getData('mailable')->from); - if (!$from) { + if (! $from) { return; } @@ -91,8 +84,6 @@ private function readFrom(EmailComposer $composer): void /** * Read the mailable cc to the email composer. - * - * @param EmailComposer $composer */ private function readCc(EmailComposer $composer): void { @@ -105,8 +96,6 @@ private function readCc(EmailComposer $composer): void /** * Read the mailable bcc to the email composer. - * - * @param EmailComposer $composer */ private function readBcc(EmailComposer $composer): void { @@ -119,8 +108,6 @@ private function readBcc(EmailComposer $composer): void /** * Read the mailable reply-to to the email composer. - * - * @param EmailComposer $composer */ private function readReplyTo(EmailComposer $composer): void { @@ -133,8 +120,6 @@ private function readReplyTo(EmailComposer $composer): void /** * Read the mailable subject to the email composer. - * - * @param EmailComposer $composer */ private function readSubject(EmailComposer $composer): void { @@ -144,7 +129,6 @@ private function readSubject(EmailComposer $composer): void /** * Read the mailable body to the email composer. * - * @param EmailComposer $composer * @throws Exception */ private function readBody(EmailComposer $composer): void @@ -158,8 +142,6 @@ private function readBody(EmailComposer $composer): void /** * Read the mailable attachments to the email composer. - * - * @param EmailComposer $composer */ private function readAttachments(EmailComposer $composer): void { diff --git a/src/Preparer.php b/src/Preparer.php index ea405d0..884cf89 100644 --- a/src/Preparer.php +++ b/src/Preparer.php @@ -11,8 +11,6 @@ class Preparer { /** * Prepare the given e-mail for database storage. - * - * @param EmailComposer $composer */ public function prepare(EmailComposer $composer): void { @@ -47,8 +45,6 @@ public function prepare(EmailComposer $composer): void /** * Prepare the label for database storage. - * - * @param EmailComposer $composer */ private function prepareLabel(EmailComposer $composer): void { @@ -63,8 +59,6 @@ private function prepareLabel(EmailComposer $composer): void /** * Prepare the recipient for database storage. - * - * @param EmailComposer $composer */ private function prepareRecipient(EmailComposer $composer): void { @@ -79,8 +73,6 @@ private function prepareRecipient(EmailComposer $composer): void /** * Prepare the from values for database storage. - * - * @param EmailComposer $composer */ private function prepareFrom(EmailComposer $composer): void { @@ -91,8 +83,6 @@ private function prepareFrom(EmailComposer $composer): void /** * Prepare the carbon copies for database storage. - * - * @param EmailComposer $composer */ private function prepareCc(EmailComposer $composer): void { @@ -107,8 +97,6 @@ private function prepareCc(EmailComposer $composer): void /** * Prepare the carbon copies for database storage. - * - * @param EmailComposer $composer */ private function prepareBcc(EmailComposer $composer): void { @@ -123,8 +111,6 @@ private function prepareBcc(EmailComposer $composer): void /** * Prepare the reply-to for database storage. - * - * @param EmailComposer $composer */ private function prepareReplyTo(EmailComposer $composer): void { @@ -150,8 +136,6 @@ private function prepareReplyTo(EmailComposer $composer): void /** * Prepare the subject for database storage. - * - * @param EmailComposer $composer */ private function prepareSubject(EmailComposer $composer): void { @@ -162,8 +146,6 @@ private function prepareSubject(EmailComposer $composer): void /** * Prepare the view for database storage. - * - * @param EmailComposer $composer */ private function prepareView(EmailComposer $composer): void { @@ -174,8 +156,6 @@ private function prepareView(EmailComposer $composer): void /** * Prepare the variables for database storage. - * - * @param EmailComposer $composer */ private function prepareVariables(EmailComposer $composer): void { @@ -190,8 +170,6 @@ private function prepareVariables(EmailComposer $composer): void /** * Prepare the e-mail body for database storage. - * - * @param EmailComposer $composer */ private function prepareBody(EmailComposer $composer): void { @@ -210,8 +188,6 @@ private function prepareBody(EmailComposer $composer): void /** * Prepare the e-mail attachments. - * - * @param EmailComposer $composer */ private function prepareAttachments(EmailComposer $composer): void { @@ -219,14 +195,14 @@ private function prepareAttachments(EmailComposer $composer): void foreach ((array) $composer->getData('attachments', []) as $attachment) { $attachments[] = [ - 'type' => 'attachment', + 'type' => 'attachment', 'attachment' => $attachment, ]; } foreach ((array) $composer->getData('rawAttachments', []) as $rawAttachment) { $attachments[] = [ - 'type' => 'rawAttachment', + 'type' => 'rawAttachment', 'attachment' => $rawAttachment, ]; } @@ -238,8 +214,6 @@ private function prepareAttachments(EmailComposer $composer): void /** * Prepare the scheduled date for database storage. - * - * @param EmailComposer $composer */ private function prepareScheduled(EmailComposer $composer): void { @@ -260,8 +234,6 @@ private function prepareScheduled(EmailComposer $composer): void /** * Prepare the e-mail so it can be sent immediately. - * - * @param EmailComposer $composer */ private function prepareImmediately(EmailComposer $composer): void { @@ -272,8 +244,6 @@ private function prepareImmediately(EmailComposer $composer): void /** * Prepare the queued date. - * - * @param EmailComposer $composer */ private function prepareQueued(EmailComposer $composer): void { @@ -283,5 +253,4 @@ private function prepareQueued(EmailComposer $composer): void ]); } } - } diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index 039ca37..481a0d6 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -33,8 +33,6 @@ class SendEmailsCommand extends Command /** * Create a new SendEmailsCommand instance. - * - * @param Store $store */ public function __construct(Store $store) { @@ -45,8 +43,6 @@ public function __construct(Store $store) /** * Execute the console command. - * - * @return void */ public function handle(): void { @@ -77,9 +73,6 @@ public function handle(): void /** * Output a table with the cronjob result. - * - * @param Collection $emails - * @return void */ protected function result(Collection $emails): void { diff --git a/src/Sender.php b/src/Sender.php index 72ce8a1..55424eb 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -11,8 +11,6 @@ class Sender { /** * Send the given e-mail. - * - * @param Email $email */ public function send(Email $email): void { @@ -27,7 +25,7 @@ public function send(Email $email): void }); // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) - if (!is_null($sentMessage)) { + if (! is_null($sentMessage)) { event(new MessageSent($sentMessage)); } @@ -36,9 +34,6 @@ public function send(Email $email): void /** * Build the e-mail message. - * - * @param Message $message - * @param Email $email */ private function buildMessage(Message $message, Email $email): void { @@ -52,7 +47,7 @@ private function buildMessage(Message $message, Email $email): void $message->html($email->getBody()); $attachmentMap = [ - 'attachment' => 'attach', + 'attachment' => 'attach', 'rawAttachment' => 'attachData', ]; diff --git a/src/SentMessage.php b/src/SentMessage.php index c58bbdb..f3dff66 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -10,13 +10,21 @@ class SentMessage { public $from = []; + public $to = []; + public $cc = []; + public $bcc = []; + public $replyTo = []; + public $subject = ''; + public $body = ''; + public $attachments = []; + public $headers = []; public static function createFromSymfonyMailer(Email $email): SentMessage diff --git a/src/Validator.php b/src/Validator.php index 69d130f..ef5979b 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -4,12 +4,13 @@ namespace Stackkit\LaravelDatabaseEmails; -use Exception; +use const FILTER_VALIDATE_EMAIL; + use Carbon\Carbon; +use Exception; use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Arr; use InvalidArgumentException; -use const FILTER_VALIDATE_EMAIL; class Validator { @@ -23,7 +24,6 @@ class Validator /** * Validate the data that was given to the e-mail composer. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ public function validate(EmailComposer $composer): void @@ -50,20 +50,18 @@ public function validate(EmailComposer $composer): void /** * Validate the defined label. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateLabel(EmailComposer $composer): void { if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) { - throw new InvalidArgumentException('The given label [' . $composer->getData('label') . '] is too large for database storage'); + throw new InvalidArgumentException('The given label ['.$composer->getData('label').'] is too large for database storage'); } } /** * Validate the given recipient(s). * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateRecipient(EmailComposer $composer): void @@ -80,7 +78,7 @@ private function validateRecipient(EmailComposer $composer): void foreach ($recipients as $recipient) { if (! filter_var($recipient, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address [' . $recipient . '] is invalid'); + throw new InvalidArgumentException('E-mail address ['.$recipient.'] is invalid'); } } } @@ -88,7 +86,6 @@ private function validateRecipient(EmailComposer $composer): void /** * Validate the carbon copy e-mail addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateCc(EmailComposer $composer): void @@ -99,7 +96,7 @@ private function validateCc(EmailComposer $composer): void foreach ((array) $composer->getData('cc') as $cc) { if (! filter_var($cc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address [' . $cc . '] is invalid'); + throw new InvalidArgumentException('E-mail address ['.$cc.'] is invalid'); } } } @@ -107,7 +104,6 @@ private function validateCc(EmailComposer $composer): void /** * Validate the blind carbon copy e-mail addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateBcc(EmailComposer $composer): void @@ -118,7 +114,7 @@ private function validateBcc(EmailComposer $composer): void foreach ((array) $composer->getData('bcc') as $bcc) { if (! filter_var($bcc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address [' . $bcc . '] is invalid'); + throw new InvalidargumentException('E-mail address ['.$bcc.'] is invalid'); } } } @@ -126,7 +122,6 @@ private function validateBcc(EmailComposer $composer): void /** * Validate the reply-to addresses. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateReplyTo(EmailComposer $composer): void @@ -141,7 +136,7 @@ private function validateReplyTo(EmailComposer $composer): void } if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address [' . $replyTo . '] is invalid'); + throw new InvalidargumentException('E-mail address ['.$replyTo.'] is invalid'); } } } @@ -149,7 +144,6 @@ private function validateReplyTo(EmailComposer $composer): void /** * Validate the e-mail subject. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateSubject(EmailComposer $composer): void @@ -162,7 +156,6 @@ private function validateSubject(EmailComposer $composer): void /** * Validate the e-mail view. * - * @param EmailComposer $composer * @throws InvalidARgumentException */ private function validateView(EmailComposer $composer): void @@ -178,14 +171,13 @@ private function validateView(EmailComposer $composer): void $view = $composer->getData('view'); if (! view()->exists($view)) { - throw new InvalidArgumentException('View [' . $view . '] does not exist'); + throw new InvalidArgumentException('View ['.$view.'] does not exist'); } } /** * Validate the e-mail variables. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateVariables(EmailComposer $composer): void @@ -198,7 +190,6 @@ private function validateVariables(EmailComposer $composer): void /** * Validate the scheduled date. * - * @param EmailComposer $composer * @throws InvalidArgumentException */ private function validateScheduled(EmailComposer $composer): void @@ -217,7 +208,7 @@ private function validateScheduled(EmailComposer $composer): void try { Carbon::parse($scheduled); } catch (Exception $e) { - throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: ' . $e->getMessage()); + throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: '.$e->getMessage()); } } } diff --git a/tests/ConfigCacheTest.php b/tests/ConfigCacheTest.php index 5b1fccc..6af6a1f 100644 --- a/tests/ConfigCacheTest.php +++ b/tests/ConfigCacheTest.php @@ -13,7 +13,7 @@ public function the_configuration_file_can_be_cached() $failed = false; try { - serialize(require __DIR__ . '/../config/laravel-database-emails.php'); + serialize(require __DIR__.'/../config/laravel-database-emails.php'); } catch (Throwable) { $failed = true; } diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 2eb67fb..dcb2197 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -29,7 +29,7 @@ public function recipient_should_be_saved_correctly() public function cc_and_bcc_should_be_saved_correctly() { $email = $this->sendEmail([ - 'cc' => $cc = [ + 'cc' => $cc = [ 'john@doe.com', ], 'bcc' => $bcc = [ @@ -199,7 +199,7 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') ->send(); $this->assertCount(1, $email->getAttachments()); @@ -207,23 +207,23 @@ public function attachments_should_be_saved_correctly() $attachment = $email->getAttachments()[0]; $this->assertEquals('attachment', $attachment['type']); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachment['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachment['attachment']['file']); $email = $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') - ->attach(__DIR__ . '/files/pdf-sample-2.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample-2.pdf') ->send(); $this->assertCount(2, $email->getAttachments()); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); - $this->assertEquals(__DIR__ . '/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); } #[Test] public function in_memory_attachments_should_be_saved_correctly() { - $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); + $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); $email = $this->composeEmail() ->attachData($rawData, 'generated.pdf', [ diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index e835512..89f4b92 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -38,7 +38,7 @@ public function the_recipient_should_be_encrypted_and_decrypted() public function cc_and_bb_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ - 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], + 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], 'bcc' => $bcc = ['jane+1@doe.com', 'jane+2@doe.com'], ]); @@ -53,7 +53,7 @@ public function cc_and_bb_should_be_encrypted_and_decrypted() public function reply_to_should_be_encrypted_and_decrypted() { $email = $this->sendEmail([ - 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], + 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], ]); $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); $this->assertEquals($replyTo, $email->getReplyTo()); @@ -64,14 +64,14 @@ public function reply_to_should_be_encrypted_and_decrypted() // Test with a single Address object... $email = $this->sendEmail([ - 'reply_to' => new Address('john+1@doe.com', 'John Doe'), + 'reply_to' => new Address('john+1@doe.com', 'John Doe'), ]); $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo()); // Address with an array of Address objects... $email = $this->sendEmail([ - 'reply_to' => [ + 'reply_to' => [ new Address('john+1@doe.com', 'John Doe'), new Address('jane+1@doe.com', 'Jane Doe'), ], @@ -125,7 +125,7 @@ public function from_should_be_encrypted_and_decrypted() $expect = [ 'address' => 'marick@dolphiq.nl', - 'name' => 'Marick', + 'name' => 'Marick', ]; $this->assertEquals($expect, decrypt($email->getRawDatabaseValue('from'))); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 6691d8e..43933c7 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -85,7 +85,7 @@ public function it_extracts_attachments() $this->assertCount(2, $attachments); $this->assertEquals('attachment', $attachments[0]['type']); - $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); $this->assertEquals('rawAttachment', $attachments[1]['type']); $this->assertEquals('order.html', $attachments[1]['attachment']['name']); @@ -140,7 +140,7 @@ public function envelope(): Envelope return new Envelope( null, [ - new Address('john@doe.com', 'John Doe') + new Address('john@doe.com', 'John Doe'), ], ['john+cc@doe.com', 'john+cc2@doe.com'], ['john+bcc@doe.com', 'john+bcc2@doe.com'], @@ -152,10 +152,10 @@ public function envelope(): Envelope public function attachments(): array { return [ - Attachment::fromPath(__DIR__ . '/files/pdf-sample.pdf')->withMime('application/pdf'), + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf')->withMime('application/pdf'), Attachment::fromData(function () { return '

Thanks for your oder

'; - }, 'order.html') + }, 'order.html'), ]; } } diff --git a/tests/PruneTest.php b/tests/PruneTest.php index ddcf8f1..b562f35 100644 --- a/tests/PruneTest.php +++ b/tests/PruneTest.php @@ -13,11 +13,11 @@ public function by_default_mails_are_pruned_after_6_months() { $email = $this->sendEmail(); - Carbon::setTestNow($email->created_at . ' + 6 months'); + Carbon::setTestNow($email->created_at.' + 6 months'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertInstanceOf(Email::class, $email->fresh()); - Carbon::setTestNow($email->created_at . ' + 6 months + 1 day'); + Carbon::setTestNow($email->created_at.' + 6 months + 1 day'); // Ensure the email object has to be passed manually, otherwise we are acidentally // deleting everyone's e-mails... @@ -38,11 +38,11 @@ public function can_change_when_emails_are_pruned() $email = $this->sendEmail(); - Carbon::setTestNow($email->created_at . ' + 3 months'); + Carbon::setTestNow($email->created_at.' + 3 months'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertInstanceOf(Email::class, $email->fresh()); - Carbon::setTestNow($email->created_at . ' + 3 months + 1 day'); + Carbon::setTestNow($email->created_at.' + 3 months + 1 day'); $this->artisan('model:prune', ['--model' => [Email::class]]); $this->assertNull($email->fresh()); } diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 560fee1..5658f3c 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -2,10 +2,10 @@ namespace Tests; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; -use Illuminate\Support\Facades\Mail; class QueuedEmailsTest extends TestCase { diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 00b52f1..dbd58b4 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -123,8 +123,8 @@ public function the_failed_status_and_error_is_cleared_if_a_previously_failed_em $email = $this->sendEmail(); $email->update([ - 'failed' => true, - 'error' => 'Simulating some random error', + 'failed' => true, + 'error' => 'Simulating some random error', 'attempts' => 1, ]); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index b6a6907..5b638b8 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -4,11 +4,11 @@ use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; -use Illuminate\Support\Facades\Mail; -use Stackkit\LaravelDatabaseEmails\Email; class SenderTest extends TestCase { @@ -162,7 +162,7 @@ public function the_email_has_the_correct_body() public function attachments_are_added_to_the_email() { $this->composeEmail() - ->attach(__DIR__ . '/files/pdf-sample.pdf') + ->attach(__DIR__.'/files/pdf-sample.pdf') ->send(); $this->artisan('email:send'); @@ -174,7 +174,7 @@ public function attachments_are_added_to_the_email() #[Test] public function raw_attachments_are_added_to_the_email() { - $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf'); + $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); $this->composeEmail() ->attachData($rawData, 'hello-ci.pdf', [ diff --git a/tests/TestCase.php b/tests/TestCase.php index 7e3a78d..2cada5b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,7 @@ namespace Tests; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; -use Illuminate\Foundation\Testing\RefreshDatabase; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -30,9 +28,9 @@ function () { }, ]; - view()->addNamespace('tests', __DIR__ . '/views'); + view()->addNamespace('tests', __DIR__.'/views'); - $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); Email::truncate(); } @@ -47,7 +45,7 @@ protected function getPackageProviders($app) /** * Define environment setup. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app * @return void */ protected function getEnvironmentSetUp($app) @@ -60,7 +58,7 @@ protected function getEnvironmentSetUp($app) $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ 'driver' => $driver, - ...match($driver) { + ...match ($driver) { 'sqlite' => [ 'database' => ':memory:', ], @@ -84,13 +82,13 @@ protected function getEnvironmentSetUp($app) public function createEmail($overwrite = []) { $params = array_merge([ - 'label' => 'welcome', + 'label' => 'welcome', 'recipient' => 'john@doe.com', - 'cc' => null, - 'bcc' => null, - 'reply_to' => null, - 'subject' => 'test', - 'view' => 'tests::dummy', + 'cc' => null, + 'bcc' => null, + 'reply_to' => null, + 'subject' => 'test', + 'view' => 'tests::dummy', 'variables' => ['name' => 'John Doe'], ], $overwrite); diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index d2afce7..9079f2d 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Workbench\Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder From 95141c84473e72108f560c07ee8ed233fec82a20 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:51:11 +0100 Subject: [PATCH 35/60] wip --- .github/workflows/run-tests.yml | 35 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 22a5b6b..39e170d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,23 +62,22 @@ jobs: needs: access_check strategy: matrix: + db: [ 'mysql', 'sqlite' ] payload: - - { laravel: '11.*', php: '8.3', 'testbench': '9.*' } - - { laravel: '11.*', php: '8.2', 'testbench': '9.*' } - - { laravel: '10.*', php: '8.3', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.2', 'testbench': '8.*' } - - { laravel: '10.*', php: '8.1', 'testbench': '8.*' } + - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } + - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } + - { laravel: '10.*', php: '8.3', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.2', 'testbench': '8.*', collision: '7.*' } + - { laravel: '10.*', php: '8.1', 'testbench': '8.*', collision: '7.*' } - name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} + name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} services: mysql: image: mysql:8 env: - MYSQL_USER: root - MYSQL_ROOT_PASSWORD: root - MYSQL_PASSWORD: - MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_USER: test + MYSQL_PASSWORD: test MYSQL_DATABASE: test ports: - 3307:3306 @@ -97,9 +96,23 @@ jobs: extensions: mbstring, dom, fileinfo, mysql coverage: none + - name: Set up MySQL and PostgreSQL + run: | + MYSQL_PORT=3307 POSTGRES_PORT=5432 docker compose up ${{ matrix.db }} -d + - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" "nunomaduro/collision:${{ matrix.payload.collision }}" --no-interaction --no-update composer update --prefer-stable --prefer-dist --no-interaction + if [ "${{ matrix.db }}" = "mysql" ]; then + while ! mysqladmin ping --host=127.0.0.1 --user=test --port=3307 --password=test --silent; do + echo "Waiting for MySQL..." + sleep 1 + done + else + echo "Not waiting for MySQL." + fi - name: Execute tests + env: + DB_DRIVER: ${{ matrix.db }} run: composer test From 4a57bcdd0aa13f4eb1717afd716d896dfb219722 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:53:56 +0100 Subject: [PATCH 36/60] rm service --- .github/workflows/run-tests.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 39e170d..063d5a9 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -72,17 +72,6 @@ jobs: name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} - services: - mysql: - image: mysql:8 - env: - MYSQL_USER: test - MYSQL_PASSWORD: test - MYSQL_DATABASE: test - ports: - - 3307:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - name: Checkout code uses: actions/checkout@v4 From 9a59cb153254bfad149ba166cc2bbbb78ca7819d Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 14:56:52 +0100 Subject: [PATCH 37/60] dont start containers for sqlite --- .github/workflows/run-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 063d5a9..184589d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -87,7 +87,9 @@ jobs: - name: Set up MySQL and PostgreSQL run: | - MYSQL_PORT=3307 POSTGRES_PORT=5432 docker compose up ${{ matrix.db }} -d + if [ "${{ matrix.db }}" != "sqlite" ]; then + MYSQL_PORT=3307 POSTGRES_PORT=5432 docker compose up ${{ matrix.db }} -d + fi - name: Install dependencies run: | From 5971218cfc2820cd893b8e82ec149236eb4c20fc Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 16 Mar 2024 16:27:30 +0100 Subject: [PATCH 38/60] wip --- composer.json | 5 ++- ...024_03_16_151608_change_binary_to_text.php | 38 +++++++++++++++++++ src/EmailComposer.php | 28 ++++++++++++++ src/HasEncryptedAttributes.php | 4 ++ src/MailableReader.php | 23 +++++++++++ tests/DatabaseInteractionTest.php | 4 +- tests/EnvelopeTest.php | 32 ++++++++++++++++ 7 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2024_03_16_151608_change_binary_to_text.php create mode 100644 tests/EnvelopeTest.php diff --git a/composer.json b/composer.json index c3df63c..0d16c4d 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ }, "require": { "ext-json": "*", - "laravel/framework": "^10.0|^11.0" + "laravel/framework": "^10.0|^11.0", + "doctrine/dbal": "^3.8" }, "require-dev": { "mockery/mockery": "^1.2", @@ -67,4 +68,4 @@ "@php vendor/bin/phpstan analyse" ] } -} \ No newline at end of file +} diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_change_binary_to_text.php new file mode 100644 index 0000000..6a196ca --- /dev/null +++ b/database/migrations/2024_03_16_151608_change_binary_to_text.php @@ -0,0 +1,38 @@ +text('recipient')->change(); + $table->text('cc')->nullable()->change(); + $table->text('bcc')->nullable()->change(); + $table->text('subject')->change(); + $table->text('variables')->nullable()->change(); + $table->text('body')->change(); + $table->text('attachments')->nullable()->change(); + $table->text('from')->nullable()->change(); + $table->text('reply_to')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 905d0a1..452eb25 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -5,6 +5,9 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; class EmailComposer { @@ -22,6 +25,9 @@ class EmailComposer */ protected $data = []; + public ?Envelope $envelope = null; + public ?Content $content = null; + /** * Create a new EmailComposer instance. */ @@ -30,6 +36,28 @@ public function __construct(Email $email) $this->email = $email; } + public function envelope(Envelope $envelope): self + { + $this->envelope = $envelope; + + if ($this->content) { + (new MailableReader())->read($this); + } + + return $this; + } + + public function content(Content $content): self + { + $this->content = $content; + + if ($this->envelope) { + (new MailableReader())->read($this); + } + + return $this; + } + /** * Get the e-mail that is being composed. */ diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index 7d02012..0c23f2d 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -48,6 +48,10 @@ public function getAttribute($key) { $value = $this->attributes[$key]; + if (is_resource($value)) { + $value = stream_get_contents($value); + } + if ($this->isEncrypted() && in_array($key, $this->encrypted)) { try { $value = decrypt($value); diff --git a/src/MailableReader.php b/src/MailableReader.php index 22a2238..2be0c0a 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -6,6 +6,9 @@ use Exception; use Illuminate\Container\Container; +use Illuminate\Mail\Mailable; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; use ReflectionObject; class MailableReader @@ -15,6 +18,26 @@ class MailableReader */ public function read(EmailComposer $composer): void { + if ($composer->envelope && $composer->content) { + $composer->setData('mailable', new class($composer) extends Mailable { + public function __construct(private EmailComposer $composer) + { + // + } + + public function content(): Content + { + return $this->composer->content; + } + + public function envelope(): Envelope + { + return $this->composer->envelope; + } + }); + } + + if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { $reflected = (new ReflectionObject($composer->getData('mailable'))); $method = $reflected->getMethod('prepareMailableForDelivery'); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index dcb2197..f1f4b0e 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -233,7 +233,7 @@ public function in_memory_attachments_should_be_saved_correctly() $this->assertCount(1, $email->getAttachments()); - $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); - $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); +// $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); +// $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); } } diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php new file mode 100644 index 0000000..37dea5c --- /dev/null +++ b/tests/EnvelopeTest.php @@ -0,0 +1,32 @@ +envelope( + (new Envelope()) + ->subject('Hey') + ->from('asdf@gmail.com') + ->to('johndoe@example.com', 'janedoe@example.com') + ) + ->content( + (new Content()) + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->send(); + + $this->assertEquals(['johndoe@example.com'], $email->recipient); + } + +} From 6458dbed7a615c2ef59796f14aaeffdcee5c1d4f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 17 Mar 2024 22:22:56 +0100 Subject: [PATCH 39/60] wip --- src/Attachment.php | 63 ++++++ src/Config.php | 8 - src/Email.php | 342 ++---------------------------- src/EmailComposer.php | 178 ++++++---------- src/Encrypter.php | 114 ---------- src/MailableReader.php | 87 ++++---- src/Preparer.php | 256 ---------------------- src/SendEmailsCommand.php | 13 +- src/Sender.php | 54 +++-- src/Validator.php | 215 ------------------- test | Bin 0 -> 16384 bytes tests/ConfigTest.php | 10 - tests/DatabaseInteractionTest.php | 115 ++++------ tests/EncryptionTest.php | 134 ------------ tests/EnvelopeTest.php | 8 +- tests/MailableReaderTest.php | 44 ++-- tests/SendEmailsCommandTest.php | 34 +-- tests/SenderTest.php | 62 ++---- tests/TestCase.php | 31 ++- tests/ValidatorTest.php | 206 ------------------ tests/files/my-file.txt | 1 + tests/test | Bin 0 -> 16384 bytes 22 files changed, 356 insertions(+), 1619 deletions(-) create mode 100644 src/Attachment.php delete mode 100644 src/Encrypter.php delete mode 100644 src/Preparer.php delete mode 100644 src/Validator.php create mode 100644 test delete mode 100644 tests/EncryptionTest.php delete mode 100644 tests/ValidatorTest.php create mode 100644 tests/files/my-file.txt create mode 100644 tests/test diff --git a/src/Attachment.php b/src/Attachment.php new file mode 100644 index 0000000..48b2220 --- /dev/null +++ b/src/Attachment.php @@ -0,0 +1,63 @@ +as = $name; + + return $this; + } + + public function withMime(string $mime) + { + $this->mime = $mime; + + return $this; + } + + public function toArray(): array + { + return [ + 'path' => $this->path, + 'disk' => $this->disk, + 'as' => $this->as, + 'mime' => $this->mime, + ]; + } +} diff --git a/src/Config.php b/src/Config.php index 8523a7b..9c6ae83 100644 --- a/src/Config.php +++ b/src/Config.php @@ -14,14 +14,6 @@ public static function maxAttemptCount(): int return max(config('laravel-database-emails.attempts', 1), 3); } - /** - * Determine if newly created e-mails should be encrypted. - */ - public static function encryptEmails(): bool - { - return config('laravel-database-emails.encrypt', false); - } - /** * Determine if newly created e-mails should be sent to the test e-mail address. */ diff --git a/src/Email.php b/src/Email.php index 18a18a3..b8cfb46 100644 --- a/src/Email.php +++ b/src/Email.php @@ -6,10 +6,10 @@ use Carbon\Carbon; use Closure; -use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; +use Throwable; /** * @property $id @@ -28,7 +28,6 @@ * @property $sending * @property $failed * @property $error - * @property $encrypted * @property $queued_at * @property $scheduled_at * @property $sent_at @@ -36,9 +35,19 @@ */ class Email extends Model { - use HasEncryptedAttributes; use Prunable; + protected $casts = [ + 'failed' => 'boolean', + 'recipient' => 'json', + 'from' => 'json', + 'cc' => 'json', + 'bcc' => 'json', + 'reply_to' => 'json', + 'variables' => 'json', + 'attachments' => 'json', + ]; + /** * The table in which the e-mails are stored. * @@ -63,64 +72,12 @@ public static function compose(): EmailComposer return new EmailComposer(new static()); } - /** - * Get the e-mail id. - */ - public function getId(): int - { - return $this->id; - } - - /** - * Get the e-mail label. - */ - public function getLabel(): ?string - { - return $this->label; - } - - /** - * Get the e-mail recipient. - * - * @return string|array - */ - public function getRecipient() - { - return $this->recipient; - } - - /** - * Get the e-mail recipient. - * - * @return string|array - */ - public function getRecipientAttribute() - { - return $this->recipient; - } - - /** - * Get the e-mail from. - */ - public function getFrom(): ?array - { - return $this->from; - } - - /** - * Get the e-mail from. - */ - public function getFromAttribute(): ?array - { - return $this->from; - } - /** * Get the e-mail from address. */ public function getFromAddress(): ?string { - return $this->from['address'] ?? config('mail.from.address'); + return $this->from['address']; } /** @@ -128,263 +85,7 @@ public function getFromAddress(): ?string */ public function getFromName(): ?string { - return $this->from['name'] ?? config('mail.from.name'); - } - - /** - * Get the e-mail recipient(s) as string. - */ - public function getRecipientsAsString(): string - { - $glue = ', '; - - return implode($glue, (array) $this->recipient); - } - - /** - * Get the e-mail CC addresses. - * - * @return array|string - */ - public function getCc() - { - return $this->cc; - } - - /** - * Get the e-mail CC addresses. - * - * @return array|string - */ - public function getCcAttribute() - { - return $this->cc; - } - - /** - * Get the e-mail BCC addresses. - * - * @return array|string - */ - public function getBcc() - { - return $this->bcc; - } - - /** - * Get the e-mail BCC addresses. - * - * @return array|string - */ - public function getBccAttribute() - { - return $this->bcc; - } - - /** - * Get the e-mail reply-to addresses. - * - * @return array|string - */ - public function getReplyTo() - { - return $this->reply_to; - } - - /** - * Get the e-mail reply-to addresses. - * - * @return array|string - */ - public function getReplyToAttribute() - { - return $this->reply_to; - } - - /** - * Get the e-mail subject. - */ - public function getSubject(): string - { - return $this->subject; - } - - /** - * Get the e-mail subject. - */ - public function getSubjectAttribute(): string - { - return $this->subject; - } - - /** - * Get the e-mail view. - */ - public function getView(): string - { - return $this->view; - } - - /** - * Get the e-mail variables. - */ - public function getVariables(): ?array - { - return $this->variables; - } - - /** - * Get the e-mail variables. - */ - public function getVariablesAttribute(): ?array - { - return $this->variables; - } - - /** - * Get the e-mail body. - */ - public function getBody(): string - { - return $this->body; - } - - /** - * Get the e-mail body. - */ - public function getBodyAttribute(): string - { - return $this->body; - } - - /** - * Get the e-mail attachments. - */ - public function getAttachments(): array - { - return $this->attachments; - } - - /** - * Get the number of times this e-mail was attempted to send. - */ - public function getAttempts(): int - { - return $this->attempts; - } - - /** - * Get the queued date. - */ - public function getQueuedDate(): ?string - { - return $this->queued_at; - } - - /** - * Get the queued date as a Carbon instance. - */ - public function getQueuedDateAsCarbon(): Carbon - { - if ($this->queued_at instanceof Carbon) { - return $this->queued_at; - } - - return Carbon::parse($this->queued_at); - } - - /** - * Get the scheduled date. - */ - public function getScheduledDate(): ?string - { - return $this->scheduled_at; - } - - /** - * Determine if the e-mail has variables defined. - */ - public function hasVariables(): bool - { - return ! is_null($this->variables); - } - - /** - * Get the scheduled date as a Carbon instance. - */ - public function getScheduledDateAsCarbon(): Carbon - { - if ($this->scheduled_at instanceof Carbon) { - return $this->scheduled_at; - } - - return Carbon::parse($this->scheduled_at); - } - - /** - * Get the send date for this e-mail. - */ - public function getSendDate(): ?string - { - return $this->sent_at; - } - - /** - * Get the send error. - * - * @return string|string - */ - public function getError(): ?string - { - return $this->error; - } - - /** - * Determine if the e-mail should be sent with custom from values. - */ - public function hasFrom(): bool - { - return is_array($this->from) && count($this->from) > 0; - } - - /** - * Determine if the e-mail should be sent as a carbon copy. - */ - public function hasCc(): bool - { - return strlen($this->getRawDatabaseValue('cc')) > 0; - } - - /** - * Determine if the e-mail should be sent as a blind carbon copy. - */ - public function hasBcc(): bool - { - return strlen($this->getRawDatabaseValue('bcc')) > 0; - } - - /** - * Determine if the e-mail should sent with reply-to. - */ - public function hasReplyTo(): bool - { - return strlen($this->getRawDatabaseValue('reply_to') ?: '') > 0; - } - - /** - * Determine if the e-mail is scheduled to be sent later. - */ - public function isScheduled(): bool - { - return ! is_null($this->getScheduledDate()); - } - - /** - * Determine if the e-mail is encrypted. - */ - public function isEncrypted(): bool - { - return (bool) $this->getRawDatabaseValue('encrypted'); + return $this->from['name']; } /** @@ -432,7 +133,7 @@ public function markAsSent(): void /** * Mark the e-mail as failed. */ - public function markAsFailed(Exception $exception): void + public function markAsFailed(Throwable $exception): void { $this->update([ 'sending' => 0, @@ -471,19 +172,6 @@ public function retry(): void $retry->save(); } - /** - * @param mixed $default - * @return mixed - */ - public function getRawDatabaseValue(?string $key = null, $default = null) - { - if (method_exists($this, 'getRawOriginal')) { - return $this->getRawOriginal($key, $default); - } - - return $this->getOriginal($key, $default); - } - /** * @return void */ diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 452eb25..8c63a92 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -4,10 +4,11 @@ namespace Stackkit\LaravelDatabaseEmails; +use Closure; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Address; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Support\Carbon; class EmailComposer { @@ -26,8 +27,11 @@ class EmailComposer protected $data = []; public ?Envelope $envelope = null; + public ?Content $content = null; + public ?array $attachments = null; + /** * Create a new EmailComposer instance. */ @@ -36,25 +40,42 @@ public function __construct(Email $email) $this->email = $email; } - public function envelope(Envelope $envelope): self + public function envelope(null|Envelope|Closure $envelope = null): self { - $this->envelope = $envelope; + if ($envelope instanceof Closure) { + $this->envelope = $envelope($this->envelope ?: new Envelope()); - if ($this->content) { - (new MailableReader())->read($this); + return $this; } + $this->envelope = $envelope; + return $this; } - public function content(Content $content): self + public function content(null|Content|Closure $content = null): self { + if ($content instanceof Closure) { + $this->content = $content($this->content ?: new Content()); + + return $this; + } + $this->content = $content; - if ($this->envelope) { - (new MailableReader())->read($this); + return $this; + } + + public function attachments(null|array|Closure $attachments = null): self + { + if ($attachments instanceof Closure) { + $this->attachments = $attachments($this->attachments ?: []); + + return $this; } + $this->attachments = $attachments; + return $this; } @@ -106,89 +127,9 @@ public function hasData(string $key): bool */ public function label(string $label): self { - return $this->setData('label', $label); - } - - /** - * Set the e-mail from address and aname. - */ - public function from(?string $address = null, ?string $name = null): self - { - return $this->setData('from', compact('address', 'name')); - } + $this->email->label = $label; - /** - * Set the e-mail recipient(s). - * - * @param string|array $recipient - */ - public function recipient($recipient): self - { - return $this->setData('recipient', $recipient); - } - - /** - * Define the carbon-copy address(es). - * - * @param string|array $cc - */ - public function cc($cc): self - { - return $this->setData('cc', $cc); - } - - /** - * Define the blind carbon-copy address(es). - * - * @param string|array $bcc - */ - public function bcc($bcc): self - { - return $this->setData('bcc', $bcc); - } - - /** - * Define the reply-to address(es). - * - * @param string|array $replyTo - */ - public function replyTo($replyTo): self - { - return $this->setData('reply_to', $replyTo); - } - - /** - * Set the e-mail subject. - */ - public function subject(string $subject): self - { - return $this->setData('subject', $subject); - } - - /** - * Set the e-mail view. - */ - public function view(string $view): self - { - return $this->setData('view', $view); - } - - /** - * Set the e-mail variables. - */ - public function variables(array $variables): self - { - return $this->setData('variables', $variables); - } - - /** - * Schedule the e-mail. - * - * @param mixed $scheduledAt - */ - public function schedule($scheduledAt): Email - { - return $this->later($scheduledAt); + return $this; } /** @@ -198,7 +139,7 @@ public function schedule($scheduledAt): Email */ public function later($scheduledAt): Email { - $this->setData('scheduled_at', $scheduledAt); + $this->email->scheduled_at = Carbon::parse($scheduledAt); return $this->send(); } @@ -213,6 +154,8 @@ public function queue(?string $connection = null, ?string $queue = null, $delay $connection = $connection ?: config('queue.default'); $queue = $queue ?: 'default'; + $this->email->queued_at = now(); + $this->setData('queued', true); $this->setData('connection', $connection); $this->setData('queue', $queue); @@ -236,38 +179,41 @@ public function mailable(Mailable $mailable): self /** * Attach a file to the e-mail. */ - public function attach(string $file, array $options = []): self - { - $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; - - $attachments[] = compact('file', 'options'); - - return $this->setData('attachments', $attachments); - } - - /** - * Attach in-memory data as an attachment. - */ - public function attachData(string $data, string $name, array $options = []): self - { - $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; - - $attachments[] = compact('data', 'name', 'options'); - - return $this->setData('rawAttachments', $attachments); - } + // public function attach(string $file, array $options = []): self + // { + // $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; + // + // $attachments[] = compact('file', 'options'); + // + // return $this->setData('attachments', $attachments); + // } + // + // /** + // * Attach in-memory data as an attachment. + // */ + // public function attachData(string $data, string $name, array $options = []): self + // { + // $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; + // + // $attachments[] = compact('data', 'name', 'options'); + // + // return $this->setData('rawAttachments', $attachments); + // } /** * Send the e-mail. */ public function send(): Email { - (new Validator())->validate($this); - - (new Preparer())->prepare($this); + if ($this->envelope && $this->content) { + (new MailableReader())->read($this); + } - if (Config::encryptEmails()) { - (new Encrypter())->encrypt($this); + if (! $this->email->from) { + $this->email->from = [ + 'address' => config('mail.from.address'), + 'name' => config('mail.from.name'), + ]; } $this->email->save(); diff --git a/src/Encrypter.php b/src/Encrypter.php deleted file mode 100644 index 7045c2c..0000000 --- a/src/Encrypter.php +++ /dev/null @@ -1,114 +0,0 @@ -setEncrypted($composer); - - $this->encryptRecipients($composer); - - $this->encryptReplyTo($composer); - - $this->encryptFrom($composer); - - $this->encryptSubject($composer); - - $this->encryptVariables($composer); - - $this->encryptBody($composer); - } - - /** - * Mark the e-mail as encrypted. - */ - private function setEncrypted(EmailComposer $composer): void - { - $composer->getEmail()->setAttribute('encrypted', 1); - } - - /** - * Encrypt the e-mail reply-to. - */ - private function encryptReplyTo(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'reply_to' => $composer->hasData('reply_to') ? encrypt($email->reply_to) : '', - ]); - } - - /** - * Encrypt the e-mail addresses of the recipients. - */ - private function encryptRecipients(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'recipient' => encrypt($email->recipient), - 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '', - 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '', - ]); - } - - /** - * Encrypt the e-mail addresses for the from field. - */ - private function encryptFrom(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'from' => encrypt($email->from), - ]); - } - - /** - * Encrypt the e-mail subject. - */ - private function encryptSubject(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'subject' => encrypt($email->subject), - ]); - } - - /** - * Encrypt the e-mail variables. - */ - private function encryptVariables(EmailComposer $composer): void - { - if (! $composer->hasData('variables')) { - return; - } - - $email = $composer->getEmail(); - - $email->fill([ - 'variables' => encrypt($email->variables), - ]); - } - - /** - * Encrypt the e-mail body. - */ - private function encryptBody(EmailComposer $composer): void - { - $email = $composer->getEmail(); - - $email->fill([ - 'body' => encrypt($email->body), - ]); - } -} diff --git a/src/MailableReader.php b/src/MailableReader.php index 2be0c0a..7f92c0d 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -4,6 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; +use Error; use Exception; use Illuminate\Container\Container; use Illuminate\Mail\Mailable; @@ -19,7 +20,8 @@ class MailableReader public function read(EmailComposer $composer): void { if ($composer->envelope && $composer->content) { - $composer->setData('mailable', new class($composer) extends Mailable { + $composer->setData('mailable', new class($composer) extends Mailable + { public function __construct(private EmailComposer $composer) { // @@ -34,10 +36,14 @@ public function envelope(): Envelope { return $this->composer->envelope; } + + public function attachments(): array + { + return $this->composer->attachments ?? []; + } }); } - if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { $reflected = (new ReflectionObject($composer->getData('mailable'))); $method = $reflected->getMethod('prepareMailableForDelivery'); @@ -71,8 +77,8 @@ public function envelope(): Envelope */ private function convertMailableAddresses($from): array { - return collect($from)->map(function ($recipient) { - return $recipient['address']; + return collect($from)->mapWithKeys(function ($recipient) { + return [$recipient['address'] => $recipient['name']]; })->toArray(); } @@ -81,11 +87,16 @@ private function convertMailableAddresses($from): array */ private function readRecipient(EmailComposer $composer): void { - $to = $this->convertMailableAddresses( - $composer->getData('mailable')->to - ); + if (config('laravel-database-emails.testing.enabled')) { + $composer->getEmail()->recipient = [ + config('laravel-database-emails.testing.email') => null, + ]; - $composer->recipient($to); + return; + } + + $composer->getEmail()->recipient = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->to); } /** @@ -93,16 +104,7 @@ private function readRecipient(EmailComposer $composer): void */ private function readFrom(EmailComposer $composer): void { - $from = reset($composer->getData('mailable')->from); - - if (! $from) { - return; - } - - $composer->from( - $from['address'], - $from['name'] - ); + $composer->getEmail()->from = head($composer->getData('mailable')->from); } /** @@ -110,11 +112,8 @@ private function readFrom(EmailComposer $composer): void */ private function readCc(EmailComposer $composer): void { - $cc = $this->convertMailableAddresses( - $composer->getData('mailable')->cc - ); - - $composer->cc($cc); + $composer->getEmail()->cc = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->cc); } /** @@ -122,11 +121,8 @@ private function readCc(EmailComposer $composer): void */ private function readBcc(EmailComposer $composer): void { - $bcc = $this->convertMailableAddresses( - $composer->getData('mailable')->bcc - ); - - $composer->bcc($bcc); + $composer->getEmail()->bcc = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->bcc); } /** @@ -134,11 +130,8 @@ private function readBcc(EmailComposer $composer): void */ private function readReplyTo(EmailComposer $composer): void { - $replyTo = $this->convertMailableAddresses( - $composer->getData('mailable')->replyTo - ); - - $composer->replyTo($replyTo); + $composer->getEmail()->reply_to = $this->prepareAddressForDatabaseStorage( + $composer->getData('mailable')->replyTo); } /** @@ -146,7 +139,7 @@ private function readReplyTo(EmailComposer $composer): void */ private function readSubject(EmailComposer $composer): void { - $composer->subject($composer->getData('mailable')->subject); + $composer->getEmail()->subject = $composer->getData('mailable')->subject; } /** @@ -156,11 +149,12 @@ private function readSubject(EmailComposer $composer): void */ private function readBody(EmailComposer $composer): void { - $composer->setData('view', ''); - + /** @var Mailable $mailable */ $mailable = $composer->getData('mailable'); - $composer->setData('body', view($mailable->view, $mailable->buildViewData())->render()); + $composer->getEmail()->view = $mailable->view; + $composer->getEmail()->variables = $mailable->buildViewData(); + $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(); } /** @@ -170,12 +164,19 @@ private function readAttachments(EmailComposer $composer): void { $mailable = $composer->getData('mailable'); - foreach ((array) $mailable->attachments as $attachment) { - call_user_func_array([$composer, 'attach'], $attachment); - } + $composer->getEmail()->attachments = array_map(function (array $attachment) { + if (! $attachment['file'] instanceof Attachment) { + throw new Error('The attachment is not an instance of '.Attachment::class.'.'); + } - foreach ((array) $mailable->rawAttachments as $rawAttachment) { - call_user_func_array([$composer, 'attachData'], $rawAttachment); - } + return $attachment['file']->toArray(); + }, $mailable->attachments); + } + + private function prepareAddressForDatabaseStorage(array $addresses): array + { + return collect($addresses)->mapWithKeys(function ($recipient) { + return [$recipient['address'] => $recipient['name']]; + })->toArray(); } } diff --git a/src/Preparer.php b/src/Preparer.php deleted file mode 100644 index 884cf89..0000000 --- a/src/Preparer.php +++ /dev/null @@ -1,256 +0,0 @@ -prepareLabel($composer); - - $this->prepareRecipient($composer); - - $this->prepareFrom($composer); - - $this->prepareCc($composer); - - $this->prepareBcc($composer); - - $this->prepareReplyTo($composer); - - $this->prepareSubject($composer); - - $this->prepareView($composer); - - $this->prepareVariables($composer); - - $this->prepareBody($composer); - - $this->prepareAttachments($composer); - - $this->prepareScheduled($composer); - - $this->prepareImmediately($composer); - - $this->prepareQueued($composer); - } - - /** - * Prepare the label for database storage. - */ - private function prepareLabel(EmailComposer $composer): void - { - if (! $composer->hasData('label')) { - return; - } - - $composer->getEmail()->fill([ - 'label' => $composer->getData('label'), - ]); - } - - /** - * Prepare the recipient for database storage. - */ - private function prepareRecipient(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->recipient(Config::testEmailAddress()); - } - - $composer->getEmail()->fill([ - 'recipient' => json_encode($composer->getData('recipient')), - ]); - } - - /** - * Prepare the from values for database storage. - */ - private function prepareFrom(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'from' => json_encode($composer->getData('from', '')), - ]); - } - - /** - * Prepare the carbon copies for database storage. - */ - private function prepareCc(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->setData('cc', []); - } - - $composer->getEmail()->fill([ - 'cc' => json_encode($composer->getData('cc', [])), - ]); - } - - /** - * Prepare the carbon copies for database storage. - */ - private function prepareBcc(EmailComposer $composer): void - { - if (Config::testing()) { - $composer->setData('bcc', []); - } - - $composer->getEmail()->fill([ - 'bcc' => json_encode($composer->getData('bcc', [])), - ]); - } - - /** - * Prepare the reply-to for database storage. - */ - private function prepareReplyTo(EmailComposer $composer): void - { - $value = $composer->getData('reply_to', []); - - if (! is_array($value)) { - $value = [$value]; - } - - foreach ($value as $i => $v) { - if ($v instanceof Address) { - $value[$i] = [ - 'address' => $v->address, - 'name' => $v->name, - ]; - } - } - - $composer->getEmail()->fill([ - 'reply_to' => json_encode($value), - ]); - } - - /** - * Prepare the subject for database storage. - */ - private function prepareSubject(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'subject' => $composer->getData('subject'), - ]); - } - - /** - * Prepare the view for database storage. - */ - private function prepareView(EmailComposer $composer): void - { - $composer->getEmail()->fill([ - 'view' => $composer->getData('view'), - ]); - } - - /** - * Prepare the variables for database storage. - */ - private function prepareVariables(EmailComposer $composer): void - { - if (! $composer->hasData('variables')) { - return; - } - - $composer->getEmail()->fill([ - 'variables' => json_encode($composer->getData('variables')), - ]); - } - - /** - * Prepare the e-mail body for database storage. - */ - private function prepareBody(EmailComposer $composer): void - { - // If the body was predefined (by for example a mailable), use that. - if ($composer->hasData('body')) { - $body = $composer->getData('body'); - } else { - $body = view( - $composer->getData('view'), - $composer->hasData('variables') ? $composer->getData('variables') : [] - )->render(); - } - - $composer->getEmail()->fill(compact('body')); - } - - /** - * Prepare the e-mail attachments. - */ - private function prepareAttachments(EmailComposer $composer): void - { - $attachments = []; - - foreach ((array) $composer->getData('attachments', []) as $attachment) { - $attachments[] = [ - 'type' => 'attachment', - 'attachment' => $attachment, - ]; - } - - foreach ((array) $composer->getData('rawAttachments', []) as $rawAttachment) { - $attachments[] = [ - 'type' => 'rawAttachment', - 'attachment' => $rawAttachment, - ]; - } - - $composer->getEmail()->fill([ - 'attachments' => serialize($attachments), - ]); - } - - /** - * Prepare the scheduled date for database storage. - */ - private function prepareScheduled(EmailComposer $composer): void - { - if (! $composer->hasData('scheduled_at')) { - return; - } - - $scheduled = $composer->getData('scheduled_at'); - - if (is_string($scheduled)) { - $scheduled = Carbon::parse($scheduled); - } - - $composer->getEmail()->fill([ - 'scheduled_at' => $scheduled->toDateTimeString(), - ]); - } - - /** - * Prepare the e-mail so it can be sent immediately. - */ - private function prepareImmediately(EmailComposer $composer): void - { - if (Config::sendImmediately()) { - $composer->getEmail()->fill(['sending' => 1]); - } - } - - /** - * Prepare the queued date. - */ - private function prepareQueued(EmailComposer $composer): void - { - if ($composer->getData('queued', false) === true) { - $composer->getEmail()->fill([ - 'queued_at' => Carbon::now()->toDateTimeString(), - ]); - } - } -} diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index 481a0d6..e8ed7fc 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -4,9 +4,10 @@ namespace Stackkit\LaravelDatabaseEmails; -use Exception; use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Arr; +use Throwable; class SendEmailsCommand extends Command { @@ -61,7 +62,7 @@ public function handle(): void try { $email->send(); - } catch (Exception $e) { + } catch (Throwable $e) { $email->markAsFailed($e); } } @@ -82,10 +83,10 @@ protected function result(Collection $emails): void $this->table($headers, $emails->map(function (Email $email) { return [ - $email->getId(), - $email->getRecipientsAsString(), - $email->getSubject(), - $email->hasFailed() ? 'Failed' : 'OK', + $email->id, + implode(',', array_column(Arr::wrap($email->recipient), 'recipient')), + $email->subject, + $email->failed ? 'Failed' : 'OK', ]; })); } diff --git a/src/Sender.php b/src/Sender.php index 55424eb..7f755c2 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -4,6 +4,7 @@ namespace Stackkit\LaravelDatabaseEmails; +use Illuminate\Mail\Attachment; use Illuminate\Mail\Message; use Illuminate\Support\Facades\Mail; @@ -37,22 +38,43 @@ public function send(Email $email): void */ private function buildMessage(Message $message, Email $email): void { - $message->to($email->getRecipient()) - ->cc($email->hasCc() ? $email->getCc() : []) - ->bcc($email->hasBcc() ? $email->getBcc() : []) - ->replyTo($email->hasReplyTo() ? $email->getReplyTo() : []) - ->subject($email->getSubject()) - ->from($email->getFromAddress(), $email->getFromName()); - - $message->html($email->getBody()); - - $attachmentMap = [ - 'attachment' => 'attach', - 'rawAttachment' => 'attachData', - ]; - - foreach ($email->getAttachments() as $attachment) { - call_user_func_array([$message, $attachmentMap[$attachment['type']]], $attachment['attachment']); + $message->to($email->recipient) + ->cc($email->cc ?: []) + ->bcc($email->bcc ?: []) + ->replyTo($email->reply_to ?: []) + ->subject($email->subject) + ->from($email->getFromAddress(), $email->getFromName()) + ->html($email->body); + + foreach ($email->attachments as $dbAttachment) { + $attachment = match (true) { + isset($dbAttachment['disk']) => Attachment::fromStorageDisk( + $dbAttachment['disk'], + $dbAttachment['path'] + ), + default => Attachment::fromPath($dbAttachment['path']), + }; + + if (! empty($dbAttachment['mime'])) { + $attachment->withMime($dbAttachment['mime']); + } + + if (! empty($dbAttachment['as'])) { + $attachment->as($dbAttachment['as']); + } + + $message->attach($attachment); } } + + public function prepAddresses(array $addresses) + { + return $addresses; + $new = []; + foreach ($addresses as $value) { + $new[$value['address']] = $value['name']; + } + + return $new; + } } diff --git a/src/Validator.php b/src/Validator.php deleted file mode 100644 index ef5979b..0000000 --- a/src/Validator.php +++ /dev/null @@ -1,215 +0,0 @@ -validateLabel($composer); - - $this->validateRecipient($composer); - - $this->validateCc($composer); - - $this->validateBcc($composer); - - $this->validateReplyTo($composer); - - $this->validateSubject($composer); - - $this->validateView($composer); - - $this->validateVariables($composer); - - $this->validateScheduled($composer); - } - - /** - * Validate the defined label. - * - * @throws InvalidArgumentException - */ - private function validateLabel(EmailComposer $composer): void - { - if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) { - throw new InvalidArgumentException('The given label ['.$composer->getData('label').'] is too large for database storage'); - } - } - - /** - * Validate the given recipient(s). - * - * @throws InvalidArgumentException - */ - private function validateRecipient(EmailComposer $composer): void - { - if (! $composer->hasData('recipient')) { - throw new InvalidArgumentException('No recipient specified'); - } - - $recipients = (array) $composer->getData('recipient'); - - if (count($recipients) == 0) { - throw new InvalidArgumentException('No recipient specified'); - } - - foreach ($recipients as $recipient) { - if (! filter_var($recipient, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address ['.$recipient.'] is invalid'); - } - } - } - - /** - * Validate the carbon copy e-mail addresses. - * - * @throws InvalidArgumentException - */ - private function validateCc(EmailComposer $composer): void - { - if (! $composer->hasData('cc')) { - return; - } - - foreach ((array) $composer->getData('cc') as $cc) { - if (! filter_var($cc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('E-mail address ['.$cc.'] is invalid'); - } - } - } - - /** - * Validate the blind carbon copy e-mail addresses. - * - * @throws InvalidArgumentException - */ - private function validateBcc(EmailComposer $composer): void - { - if (! $composer->hasData('bcc')) { - return; - } - - foreach ((array) $composer->getData('bcc') as $bcc) { - if (! filter_var($bcc, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address ['.$bcc.'] is invalid'); - } - } - } - - /** - * Validate the reply-to addresses. - * - * @throws InvalidArgumentException - */ - private function validateReplyTo(EmailComposer $composer): void - { - if (! $composer->hasData('reply_to')) { - return; - } - - foreach (Arr::wrap($composer->getData('reply_to')) as $replyTo) { - if ($replyTo instanceof Address) { - $replyTo = $replyTo->address; - } - - if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { - throw new InvalidargumentException('E-mail address ['.$replyTo.'] is invalid'); - } - } - } - - /** - * Validate the e-mail subject. - * - * @throws InvalidArgumentException - */ - private function validateSubject(EmailComposer $composer): void - { - if (! $composer->hasData('subject')) { - throw new InvalidArgumentException('No subject specified'); - } - } - - /** - * Validate the e-mail view. - * - * @throws InvalidARgumentException - */ - private function validateView(EmailComposer $composer): void - { - if ($composer->hasData('mailable')) { - return; - } - - if (! $composer->hasData('view')) { - throw new InvalidArgumentException('No view specified'); - } - - $view = $composer->getData('view'); - - if (! view()->exists($view)) { - throw new InvalidArgumentException('View ['.$view.'] does not exist'); - } - } - - /** - * Validate the e-mail variables. - * - * @throws InvalidArgumentException - */ - private function validateVariables(EmailComposer $composer): void - { - if ($composer->hasData('variables') && ! is_array($composer->getData('variables'))) { - throw new InvalidArgumentException('Variables must be an array'); - } - } - - /** - * Validate the scheduled date. - * - * @throws InvalidArgumentException - */ - private function validateScheduled(EmailComposer $composer): void - { - if (! $composer->hasData('scheduled_at')) { - return; - } - - $scheduled = $composer->getData('scheduled_at'); - - if (! $scheduled instanceof Carbon && ! is_string($scheduled)) { - throw new InvalidArgumentException('Scheduled date must be a Carbon\Carbon instance or a strtotime-valid string'); - } - - if (is_string($scheduled)) { - try { - Carbon::parse($scheduled); - } catch (Exception $e) { - throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: '.$e->getMessage()); - } - } - } -} diff --git a/test b/test new file mode 100644 index 0000000000000000000000000000000000000000..d79a5857e4f08dd52b2c70d86e8b68487b55dbca GIT binary patch literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5assertEquals(5, Config::maxAttemptCount()); } - #[Test] - public function test_encrypt_emails() - { - $this->assertFalse(Config::encryptEmails()); - - $this->app['config']->set('laravel-database-emails.encrypt', true); - - $this->assertTrue(Config::encryptEmails()); - } - #[Test] public function test_testing() { diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index f1f4b0e..4ce0e36 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -3,8 +3,10 @@ namespace Tests; use Carbon\Carbon; +use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\DB; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Attachment; class DatabaseInteractionTest extends TestCase { @@ -14,7 +16,7 @@ public function label_should_be_saved_correctly() $email = $this->sendEmail(['label' => 'welcome-email']); $this->assertEquals('welcome-email', DB::table('emails')->find(1)->label); - $this->assertEquals('welcome-email', $email->getLabel()); + $this->assertEquals('welcome-email', $email->label); } #[Test] @@ -22,7 +24,7 @@ public function recipient_should_be_saved_correctly() { $email = $this->sendEmail(['recipient' => 'john@doe.com']); - $this->assertEquals('john@doe.com', $email->getRecipient()); + $this->assertEquals(['john@doe.com' => null], $email->recipient); } #[Test] @@ -37,26 +39,20 @@ public function cc_and_bcc_should_be_saved_correctly() ], ]); - $this->assertEquals(json_encode($cc), DB::table('emails')->find(1)->cc); - $this->assertTrue($email->hasCc()); - $this->assertEquals(['john@doe.com'], $email->getCc()); - $this->assertEquals(json_encode($bcc), DB::table('emails')->find(1)->bcc); - $this->assertTrue($email->hasBcc()); - $this->assertEquals(['jane@doe.com'], $email->getBcc()); + $this->assertEquals(['john@doe.com' => null], $email->cc); + $this->assertEquals(['jane@doe.com' => null], $email->bcc); } #[Test] public function reply_to_should_be_saved_correctly() { $email = $this->sendEmail([ - 'reply_to' => $replyTo = [ + 'reply_to' => [ 'john@doe.com', ], ]); - $this->assertEquals(json_encode($replyTo), DB::table('emails')->find(1)->reply_to); - $this->assertTrue($email->hasReplyTo()); - $this->assertEquals(['john@doe.com'], $email->getReplyTo()); + $this->assertEquals(['john@doe.com' => null], $email->reply_to); } #[Test] @@ -65,7 +61,7 @@ public function subject_should_be_saved_correclty() $email = $this->sendEmail(['subject' => 'test subject']); $this->assertEquals('test subject', DB::table('emails')->find(1)->subject); - $this->assertEquals('test subject', $email->getSubject()); + $this->assertEquals('test subject', $email->subject); } #[Test] @@ -74,23 +70,7 @@ public function view_should_be_saved_correctly() $email = $this->sendEmail(['view' => 'tests::dummy']); $this->assertEquals('tests::dummy', DB::table('emails')->find(1)->view); - $this->assertEquals('tests::dummy', $email->getView()); - } - - #[Test] - public function encrypted_should_be_saved_correctly() - { - $email = $this->sendEmail(); - - $this->assertEquals(0, DB::table('emails')->find(1)->encrypted); - $this->assertFalse($email->isEncrypted()); - - $this->app['config']['laravel-database-emails.encrypt'] = true; - - $email = $this->sendEmail(); - - $this->assertEquals(1, DB::table('emails')->find(2)->encrypted); - $this->assertTrue($email->isEncrypted()); + $this->assertEquals('tests::dummy', $email->view); } #[Test] @@ -98,12 +78,12 @@ public function scheduled_date_should_be_saved_correctly() { $email = $this->sendEmail(); $this->assertNull(DB::table('emails')->find(1)->scheduled_at); - $this->assertNull($email->getScheduledDate()); + $this->assertNull($email->scheduled_at); Carbon::setTestNow(Carbon::create(2019, 1, 1, 1, 2, 3)); $email = $this->scheduleEmail('+2 weeks'); $this->assertNotNull(DB::table('emails')->find(2)->scheduled_at); - $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate()); + $this->assertEquals('2019-01-15 01:02:03', $email->scheduled_at); } #[Test] @@ -114,7 +94,7 @@ public function the_body_should_be_saved_correctly() $expectedBody = "Name: Jane Doe\n"; $this->assertSame($expectedBody, DB::table('emails')->find(1)->body); - $this->assertSame($expectedBody, $email->getBody()); + $this->assertSame($expectedBody, $email->body); } #[Test] @@ -122,13 +102,14 @@ public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); - $this->assertFalse($email->hasFrom()); - $this->assertEquals(config('mail.from.address'), $email->getFromAddress()); - $this->assertEquals(config('mail.from.name'), $email->getFromName()); + $this->assertEquals($email->from['address'], $email->getFromAddress()); + $this->assertEquals($email->from['name'], $email->getFromName()); - $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); + $email = $this->composeEmail([ + 'from' => new Address('marick@dolphiq.nl', 'Marick'), + ])->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); $this->assertEquals('Marick', $email->getFromName()); } @@ -139,7 +120,7 @@ public function variables_should_be_saved_correctly() $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); $this->assertEquals(json_encode(['name' => 'John Doe'], 1), DB::table('emails')->find(1)->variables); - $this->assertEquals(['name' => 'John Doe'], $email->getVariables()); + $this->assertEquals(['name' => 'John Doe'], $email->variables); } #[Test] @@ -148,7 +129,7 @@ public function the_sent_date_should_be_null() $email = $this->sendEmail(); $this->assertNull(DB::table('emails')->find(1)->sent_at); - $this->assertNull($email->getSendDate()); + $this->assertNull($email->sent_at); } #[Test] @@ -166,7 +147,7 @@ public function attempts_should_be_zero() $email = $this->sendEmail(); $this->assertEquals(0, DB::table('emails')->find(1)->attempts); - $this->assertEquals(0, $email->getAttempts()); + $this->assertEquals(0, $email->attempts); } #[Test] @@ -178,8 +159,7 @@ public function the_scheduled_date_should_be_saved_correctly() $email = $this->scheduleEmail('+2 weeks'); - $this->assertTrue($email->isScheduled()); - $this->assertEquals($scheduledFor, $email->getScheduledDate()); + $this->assertEquals($scheduledFor, $email->scheduled_at); } #[Test] @@ -192,48 +172,43 @@ public function recipient_should_be_swapped_for_test_address_when_in_testing_mod $email = $this->sendEmail(['recipient' => 'jane@doe.com']); - $this->assertEquals('test@address.com', $email->getRecipient()); + $this->assertEquals(['test@address.com' => null], $email->recipient); } #[Test] public function attachments_should_be_saved_correctly() { $email = $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') - ->send(); - - $this->assertCount(1, $email->getAttachments()); - - $attachment = $email->getAttachments()[0]; - - $this->assertEquals('attachment', $attachment['type']); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachment['attachment']['file']); - - $email = $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') - ->attach(__DIR__.'/files/pdf-sample-2.pdf') + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/pdf-sample2.pdf'), + // Attachment::fromStorage('pdf-sample.pdf'), + Attachment::fromStorageDisk('my-custom-disk', 'pdf-sample-2.pdf'), + ]) ->send(); - $this->assertCount(2, $email->getAttachments()); + $this->assertCount(3, $email->attachments); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']); - $this->assertEquals(__DIR__.'/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']); + $this->assertEquals( + [ + 'path' => __DIR__.'/files/pdf-sample.pdf', + 'disk' => null, + 'as' => null, + 'mime' => null, + ], + $email->attachments[0] + ); } #[Test] - public function in_memory_attachments_should_be_saved_correctly() + public function in_memory_attachments_are_not_supported() { - $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); + $this->expectExceptionMessage('Raw attachments are not supported in the database email driver.'); - $email = $this->composeEmail() - ->attachData($rawData, 'generated.pdf', [ - 'mime' => 'application/pdf', + $this->composeEmail() + ->attachments([ + Attachment::fromData(fn () => file_get_contents(__DIR__.'/files/pdf-sample.pdf'), 'pdf-sample'), ]) ->send(); - - $this->assertCount(1, $email->getAttachments()); - -// $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); -// $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); } } diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php deleted file mode 100644 index 89f4b92..0000000 --- a/tests/EncryptionTest.php +++ /dev/null @@ -1,134 +0,0 @@ -app['config']['laravel-database-emails.encrypt'] = true; - - $this->sendEmail(); - } - - #[Test] - public function an_email_should_be_marked_as_encrypted() - { - $email = $this->sendEmail(); - - $this->assertTrue($email->isEncrypted()); - } - - #[Test] - public function the_recipient_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['recipient' => 'john@doe.com']); - - $this->assertEquals('john@doe.com', decrypt($email->getRawDatabaseValue('recipient'))); - - $this->assertEquals('john@doe.com', $email->getRecipient()); - } - - #[Test] - public function cc_and_bb_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail([ - 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'], - 'bcc' => $bcc = ['jane+1@doe.com', 'jane+2@doe.com'], - ]); - - $this->assertEquals($cc, decrypt($email->getRawDatabaseValue('cc'))); - $this->assertEquals($bcc, decrypt($email->getRawDatabaseValue('bcc'))); - - $this->assertEquals($cc, $email->getCc()); - $this->assertEquals($bcc, $email->getBcc()); - } - - #[Test] - public function reply_to_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail([ - 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'], - ]); - $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertEquals($replyTo, $email->getReplyTo()); - - if (! class_exists(Address::class)) { - return; - } - - // Test with a single Address object... - $email = $this->sendEmail([ - 'reply_to' => new Address('john+1@doe.com', 'John Doe'), - ]); - $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo()); - - // Address with an array of Address objects... - $email = $this->sendEmail([ - 'reply_to' => [ - new Address('john+1@doe.com', 'John Doe'), - new Address('jane+1@doe.com', 'Jane Doe'), - ], - ]); - $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], decrypt($email->getRawDatabaseValue('reply_to'))); - $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo()); - } - - #[Test] - public function the_subject_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['subject' => 'test subject']); - - $this->assertEquals('test subject', decrypt($email->getRawDatabaseValue('subject'))); - - $this->assertEquals('test subject', $email->getSubject()); - } - - #[Test] - public function the_variables_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); - - $this->assertEquals( - ['name' => 'Jane Doe'], - decrypt($email->getRawDatabaseValue('variables')) - ); - - $this->assertEquals( - ['name' => 'Jane Doe'], - $email->getVariables() - ); - } - - #[Test] - public function the_body_should_be_encrypted_and_decrypted() - { - $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]); - - $expectedBody = "Name: Jane Doe\n"; - - $this->assertEquals($expectedBody, decrypt($email->getRawDatabaseValue('body'))); - - $this->assertEquals($expectedBody, $email->getBody()); - } - - #[Test] - public function from_should_be_encrypted_and_decrypted() - { - $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); - - $expect = [ - 'address' => 'marick@dolphiq.nl', - 'name' => 'Marick', - ]; - - $this->assertEquals($expect, decrypt($email->getRawDatabaseValue('from'))); - $this->assertEquals($expect, $email->getFrom()); - } -} diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index 37dea5c..d09d0e3 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -17,7 +17,7 @@ public function test_it_can_set_the_envelope() (new Envelope()) ->subject('Hey') ->from('asdf@gmail.com') - ->to('johndoe@example.com', 'janedoe@example.com') + ->to(['johndoe@example.com', 'janedoe@example.com']) ) ->content( (new Content()) @@ -26,7 +26,9 @@ public function test_it_can_set_the_envelope() ) ->send(); - $this->assertEquals(['johndoe@example.com'], $email->recipient); + $this->assertEquals([ + 'johndoe@example.com' => null, + 'janedoe@example.com' => null, + ], $email->recipient); } - } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 43933c7..3129df8 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -4,10 +4,10 @@ use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Address; -use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use PHPUnit\Framework\Attributes\Test; +use Stackkit\LaravelDatabaseEmails\Attachment; use Stackkit\LaravelDatabaseEmails\Email; class MailableReaderTest extends TestCase @@ -23,16 +23,16 @@ public function it_extracts_the_recipient() $composer = Email::compose() ->mailable($this->mailable()); - $this->assertEquals(['john@doe.com'], $composer->getData('recipient')); + $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->getEmail()->recipient); $composer = Email::compose() ->mailable( $this->mailable()->to(['jane@doe.com']) ); - $this->assertCount(2, $composer->getData('recipient')); - $this->assertContains('john@doe.com', $composer->getData('recipient')); - $this->assertContains('jane@doe.com', $composer->getData('recipient')); + $this->assertCount(2, $composer->getEmail()->recipient); + $this->assertArrayHasKey('john@doe.com', $composer->getEmail()->recipient); + $this->assertArrayHasKey('jane@doe.com', $composer->getEmail()->recipient); } #[Test] @@ -40,7 +40,7 @@ public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc')); + $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->getEmail()->cc); } #[Test] @@ -48,7 +48,7 @@ public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc')); + $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->getEmail()->bcc); } #[Test] @@ -56,7 +56,7 @@ public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to')); + $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->getEmail()->reply_to); } #[Test] @@ -64,7 +64,7 @@ public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals('Your order has shipped!', $composer->getData('subject')); + $this->assertEquals('Your order has shipped!', $composer->getEmail()->subject); } #[Test] @@ -72,7 +72,7 @@ public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals("Name: John Doe\n", $composer->getData('body')); + $this->assertEquals("Name: John Doe\n", $composer->getEmail()->body); } #[Test] @@ -80,16 +80,11 @@ public function it_extracts_attachments() { $email = Email::compose()->mailable($this->mailable())->send(); - $attachments = $email->getAttachments(); + $attachments = $email->attachments; $this->assertCount(2, $attachments); - $this->assertEquals('attachment', $attachments[0]['type']); - $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['attachment']['file']); - - $this->assertEquals('rawAttachment', $attachments[1]['type']); - $this->assertEquals('order.html', $attachments[1]['attachment']['name']); - $this->assertEquals('

Thanks for your oder

', $attachments[1]['attachment']['data']); + $this->assertEquals(__DIR__.'/files/pdf-sample.pdf', $attachments[0]['path']); } #[Test] @@ -100,7 +95,7 @@ public function it_extracts_the_from_address_and_or_name() ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); $this->assertEquals('Marick', $email->getFromName()); @@ -109,16 +104,17 @@ public function it_extracts_the_from_address_and_or_name() ->from('marick@dolphiq.nl') )->send(); - $this->assertTrue($email->hasFrom()); + $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals(config('mail.from.name'), $email->getFromName()); + $this->assertEquals(null, $email->getFromName()); $email = Email::compose()->mailable( ($this->mailable()) - ->from(null, 'Marick') + ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertFalse($email->hasFrom()); + $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); + $this->assertEquals('Marick', $email->getFromName()); } } @@ -153,9 +149,7 @@ public function attachments(): array { return [ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf')->withMime('application/pdf'), - Attachment::fromData(function () { - return '

Thanks for your oder

'; - }, 'order.html'), + Attachment::fromStorageDisk(__DIR__.'/files/pdf-sample.pdf', 'my-local-disk')->withMime('application/pdf'), ]; } } diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index dbd58b4..43b75f1 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -17,7 +17,7 @@ public function an_email_should_be_marked_as_sent() $this->artisan('email:send'); - $this->assertNotNull($email->fresh()->getSendDate()); + $this->assertNotNull($email->fresh()->sent_at); } #[Test] @@ -25,11 +25,11 @@ public function the_number_of_attempts_should_be_incremented() { $email = $this->sendEmail(); - $this->assertEquals(0, $email->fresh()->getAttempts()); + $this->assertEquals(0, $email->fresh()->attempts); $this->artisan('email:send'); - $this->assertEquals(1, $email->fresh()->getAttempts()); + $this->assertEquals(1, $email->fresh()->attempts); } #[Test] @@ -39,12 +39,12 @@ public function an_email_should_not_be_sent_once_it_is_marked_as_sent() $this->artisan('email:send'); - $this->assertNotNull($firstSend = $email->fresh()->getSendDate()); + $this->assertNotNull($firstSend = $email->fresh()->sent_at); $this->artisan('email:send'); - $this->assertEquals(1, $email->fresh()->getAttempts()); - $this->assertEquals($firstSend, $email->fresh()->getSendDate()); + $this->assertEquals(1, $email->fresh()->attempts); + $this->assertEquals($firstSend, $email->fresh()->sent_at); } #[Test] @@ -56,7 +56,7 @@ public function an_email_should_not_be_sent_if_it_is_queued() $this->artisan('email:send'); - $this->assertNull($email->fresh()->getSendDate()); + $this->assertNull($email->fresh()->sent_at); } #[Test] @@ -64,12 +64,12 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database { $email = $this->sendEmail(); - $email->update(['recipient' => 'asdf']); + $email->update(['recipient' => ['asdf' => null]]); $this->artisan('email:send'); $this->assertTrue($email->fresh()->hasFailed()); - $this->assertStringContainsString('RfcComplianceException', $email->fresh()->getError()); + $this->assertStringContainsString('RfcComplianceException', $email->fresh()->error); } #[Test] @@ -92,14 +92,14 @@ public function an_email_should_never_be_sent_before_its_scheduled_date() $email = $this->scheduleEmail(Carbon::now()->addHour(1)); $this->artisan('email:send'); $email = $email->fresh(); - $this->assertEquals(0, $email->getAttempts()); - $this->assertNull($email->getSendDate()); + $this->assertEquals(0, $email->attempts); + $this->assertNull($email->sent_at); $email->update(['scheduled_at' => Carbon::now()->toDateTimeString()]); $this->artisan('email:send'); $email = $email->fresh(); - $this->assertEquals(1, $email->getAttempts()); - $this->assertNotNull($email->getSendDate()); + $this->assertEquals(1, $email->attempts); + $this->assertNotNull($email->sent_at); } #[Test] @@ -128,12 +128,12 @@ public function the_failed_status_and_error_is_cleared_if_a_previously_failed_em 'attempts' => 1, ]); - $this->assertTrue($email->fresh()->hasFailed()); - $this->assertEquals('Simulating some random error', $email->fresh()->getError()); + $this->assertTrue($email->fresh()->failed); + $this->assertEquals('Simulating some random error', $email->fresh()->error); $this->artisan('email:send'); - $this->assertFalse($email->fresh()->hasFailed()); - $this->assertEmpty($email->fresh()->getError()); + $this->assertFalse($email->fresh()->failed); + $this->assertEmpty($email->fresh()->error); } } diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 5b638b8..159295d 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Mail; use PHPUnit\Framework\Attributes\Test; +use RuntimeException; +use Stackkit\LaravelDatabaseEmails\Attachment; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\MessageSent; use Stackkit\LaravelDatabaseEmails\SentMessage; @@ -54,7 +56,7 @@ public function the_email_has_a_correct_from_email_and_from_name() // custom from... $this->sent = []; - $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send(); + $this->composeEmail(['from' => new Address('marick@dolphiq.nl', 'Marick')])->send(); $this->artisan('email:send'); $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); @@ -62,19 +64,11 @@ public function the_email_has_a_correct_from_email_and_from_name() // only address $this->sent = []; - $this->composeEmail()->from('marick@dolphiq.nl')->send(); + $this->composeEmail(['from' => 'marick@dolphiq.nl'])->send(); $this->artisan('email:send'); $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); - $this->assertEquals(config('mail.from.name'), $from[key($from)]); - - // only name - $this->sent = []; - $this->composeEmail()->from(null, 'Marick')->send(); - $this->artisan('email:send'); - $from = reset($this->sent)->from; - $this->assertEquals(config('mail.from.address'), key($from)); - $this->assertEquals('Marick', $from[key($from)]); + $this->assertEquals(null, $from[key($from)]); } #[Test] @@ -162,49 +156,31 @@ public function the_email_has_the_correct_body() public function attachments_are_added_to_the_email() { $this->composeEmail() - ->attach(__DIR__.'/files/pdf-sample.pdf') + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + ]) ->send(); $this->artisan('email:send'); $attachments = reset($this->sent)->attachments; - $this->assertCount(1, $attachments); + $this->assertCount(2, $attachments); + $this->assertEquals('Test123'."\n", $attachments[1]['body']); + $this->assertEquals('text/plain disposition: attachment filename: Test123 file', $attachments[1]['disposition']); } #[Test] - public function raw_attachments_are_added_to_the_email() + public function raw_attachments_are_not_added_to_the_email() { - $rawData = file_get_contents(__DIR__.'/files/pdf-sample.pdf'); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Raw attachments are not supported in the database email driver.'); $this->composeEmail() - ->attachData($rawData, 'hello-ci.pdf', [ - 'mime' => 'application/pdf', + ->attachments([ + Attachment::fromData(fn () => 'test', 'test.txt'), ]) ->send(); - $this->artisan('email:send'); - - $attachments = reset($this->sent)->attachments; - $attachment = reset($attachments); - - $this->assertCount(1, $attachments); - $this->assertStringContainsString('hello-ci.pdf', $attachment['disposition']); - $this->assertStringContainsString('application/pdf', $attachment['disposition']); - $this->assertTrue(md5($attachment['body']) == md5($rawData)); - } - - #[Test] - public function old_json_encoded_attachments_can_still_be_read() - { - $email = $this->sendEmail(); - $email->attachments = json_encode([1, 2, 3]); - $email->save(); - - $this->assertEquals([1, 2, 3], $email->fresh()->getAttachments()); - - $email->attachments = serialize([4, 5, 6]); - $email->save(); - - $this->assertEquals([4, 5, 6], $email->fresh()->getAttachments()); } #[Test] @@ -240,10 +216,6 @@ public function it_adds_the_reply_to_addresses() $this->assertArrayHasKey('replyto1@test.com', $replyTo); $this->assertArrayHasKey('replyto2@test.com', $replyTo); - if (! class_exists(Address::class)) { - return; - } - $this->sent = []; $this->sendEmail([ 'reply_to' => new Address('replyto@test.com', 'NoReplyTest'), diff --git a/tests/TestCase.php b/tests/TestCase.php index 2cada5b..c0fc03d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,6 +3,8 @@ namespace Tests; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Mail\Mailables\Content; +use Illuminate\Mail\Mailables\Envelope; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -54,6 +56,11 @@ protected function getEnvironmentSetUp($app) $app['config']->set('laravel-database-emails.testing.enabled', false); $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); + $app['config']->set('filesystems.disks.my-custom-disk', [ + 'driver' => 'local', + 'root' => storage_path('app'), + ]); + $app['config']->set('database.default', 'testbench'); $driver = env('DB_DRIVER', 'sqlite'); $app['config']->set('database.connections.testbench', [ @@ -90,17 +97,25 @@ public function createEmail($overwrite = []) 'subject' => 'test', 'view' => 'tests::dummy', 'variables' => ['name' => 'John Doe'], + 'from' => null, ], $overwrite); return Email::compose() ->label($params['label']) - ->recipient($params['recipient']) - ->cc($params['cc']) - ->bcc($params['bcc']) - ->replyTo($params['reply_to']) - ->subject($params['subject']) - ->view($params['view']) - ->variables($params['variables']); + ->envelope( + (new Envelope()) + ->to($params['recipient']) + ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) + ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) + ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) + ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) + ->subject($params['subject']) + ) + ->content( + (new Content()) + ->view($params['view']) + ->with($params['variables']) + ); } public function composeEmail($overwrite = []) @@ -115,7 +130,7 @@ public function sendEmail($overwrite = []) public function scheduleEmail($scheduledFor, $overwrite = []) { - return $this->createEmail($overwrite)->schedule($scheduledFor); + return $this->createEmail($overwrite)->later($scheduledFor); } public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = []) diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php deleted file mode 100644 index 45ff991..0000000 --- a/tests/ValidatorTest.php +++ /dev/null @@ -1,206 +0,0 @@ -expectException(InvalidArgumentException::class); - - Email::compose() - ->label(str_repeat('a', 256)) - ->send(); - } - - #[Test] - public function a_recipient_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No recipient specified'); - - Email::compose() - ->send(); - } - - #[Test] - public function a_recipient_cannot_be_empty() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No recipient specified'); - - Email::compose() - ->recipient([]) - ->send(); - } - - #[Test] - public function the_recipient_email_must_be_valid() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('not-a-valid-email-address') - ->send(); - } - - #[Test] - public function cc_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->cc([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function bcc_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->bcc([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function reply_to_must_contain_valid_email_addresses() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); - - Email::compose() - ->recipient('john@doe.com') - ->replyTo([ - 'jane@doe.com', - 'not-a-valid-email-address', - ]) - ->send(); - } - - #[Test] - public function a_subject_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No subject specified'); - - Email::compose() - ->recipient('john@doe.com') - ->send(); - } - - #[Test] - public function a_view_is_required() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No view specified'); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->send(); - } - - #[Test] - public function the_view_must_exist() - { - // this view exists, if error thrown -> fail test - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->send(); - } catch (InvalidArgumentException $e) { - $this->fail('Expected view [tests::dummy] to exist but it does not'); - } - - // this view does not exist -> expect exception - $this->expectException(InvalidArgumentException::class); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::does-not-exist') - ->send(); - } - - #[Test] - public function variables_must_be_defined_as_an_array() - { - $email = Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy'); - - foreach ($this->invalid as $type) { - try { - $email->variables($type)->send(); - $this->fail('Expected exception to be thrown'); - } catch (\TypeError $e) { - $this->assertEquals($e->getCode(), 0); - } - } - - $valid = []; - - try { - $email->variables($valid)->send(); - } catch (InvalidArgumentException $e) { - $this->fail('Did not expect exception to be thrown'); - } - } - - #[Test] - public function the_scheduled_date_must_be_a_carbon_instance_or_a_valid_date() - { - // invalid - foreach ($this->invalid as $value) { - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule($value); - $this->fail('Expected exception to be thrown'); - } catch (InvalidArgumentException $e) { - $this->assertEquals(0, $e->getCode()); - } - } - - // valid - try { - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule('+2 week'); - - Email::compose() - ->recipient('john@doe.com') - ->subject('test') - ->view('tests::dummy') - ->schedule(Carbon\Carbon::now()); - } catch (InvalidArgumentException $e) { - $this->fail('Dit not expect exception to be thrown'); - } - } -} diff --git a/tests/files/my-file.txt b/tests/files/my-file.txt new file mode 100644 index 0000000..8f214dd --- /dev/null +++ b/tests/files/my-file.txt @@ -0,0 +1 @@ +Test123 diff --git a/tests/test b/tests/test new file mode 100644 index 0000000000000000000000000000000000000000..d79a5857e4f08dd52b2c70d86e8b68487b55dbca GIT binary patch literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5 Date: Sun, 17 Mar 2024 22:27:37 +0100 Subject: [PATCH 40/60] wip --- src/Email.php | 4 ++-- tests/TestCase.php | 23 ++++++++++------------- tests/test | Bin 16384 -> 0 bytes 3 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 tests/test diff --git a/src/Email.php b/src/Email.php index b8cfb46..a07c81c 100644 --- a/src/Email.php +++ b/src/Email.php @@ -7,8 +7,8 @@ use Carbon\Carbon; use Closure; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Prunable; use Throwable; /** @@ -35,7 +35,7 @@ */ class Email extends Model { - use Prunable; + use MassPrunable; protected $casts = [ 'failed' => 'boolean', diff --git a/tests/TestCase.php b/tests/TestCase.php index c0fc03d..c5f46cc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -102,19 +102,16 @@ public function createEmail($overwrite = []) return Email::compose() ->label($params['label']) - ->envelope( - (new Envelope()) - ->to($params['recipient']) - ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) - ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) - ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) - ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) - ->subject($params['subject']) - ) - ->content( - (new Content()) - ->view($params['view']) - ->with($params['variables']) + ->envelope(fn (Envelope $envelope) => $envelope + ->to($params['recipient']) + ->when($params['cc'], fn ($envelope) => $envelope->cc($params['cc'])) + ->when($params['bcc'], fn ($envelope) => $envelope->bcc($params['bcc'])) + ->when($params['reply_to'], fn ($envelope) => $envelope->replyTo($params['reply_to'])) + ->when($params['from'], fn (Envelope $envelope) => $envelope->from($params['from'])) + ->subject($params['subject'])) + ->content(fn (Content $content) => $content + ->view($params['view']) + ->with($params['variables']) ); } diff --git a/tests/test b/tests/test deleted file mode 100644 index d79a5857e4f08dd52b2c70d86e8b68487b55dbca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5 Date: Sat, 23 Mar 2024 00:27:54 +0100 Subject: [PATCH 41/60] wip --- .gitignore | 2 + README.md | 172 +++++++++--------- UPGRADING.md | 37 ++++ ...atabase-emails.php => database-emails.php} | 33 +--- ...024_03_16_151608_change_binary_to_text.php | 35 +++- src/Attachment.php | 12 +- src/Config.php | 10 +- src/Email.php | 119 +++--------- src/EmailComposer.php | 23 +++ src/LaravelDatabaseEmailsServiceProvider.php | 12 +- src/MailableReader.php | 29 +-- src/SendEmailsCommand.php | 2 + src/Sender.php | 13 +- test | Bin 16384 -> 49152 bytes testbench.yaml | 2 +- tests/ConfigCacheTest.php | 2 +- tests/ConfigTest.php | 8 +- tests/DatabaseInteractionTest.php | 13 +- tests/EnvelopeTest.php | 100 ++++++++++ tests/MailableReaderTest.php | 12 +- tests/SendEmailsCommandTest.php | 2 +- tests/SenderTest.php | 8 +- tests/TestCase.php | 10 +- workbench/app/Models/User.php | 11 ++ .../app/Models/UserWithPreferredEmail.php | 13 ++ .../app/Models/UserWithPreferredLocale.php | 15 ++ .../app/Models/UserWithPreferredName.php | 13 ++ .../Providers/WorkbenchServiceProvider.php | 5 +- workbench/lang/en/messages.php | 5 + workbench/lang/fil-PH/messages.php | 5 + .../resources/views/locale-email.blade.php | 2 + workbench/storage/app/public/test.txt | 1 + 32 files changed, 437 insertions(+), 289 deletions(-) create mode 100644 UPGRADING.md rename config/{laravel-database-emails.php => database-emails.php} (61%) create mode 100644 workbench/app/Models/User.php create mode 100644 workbench/app/Models/UserWithPreferredEmail.php create mode 100644 workbench/app/Models/UserWithPreferredLocale.php create mode 100644 workbench/app/Models/UserWithPreferredName.php create mode 100644 workbench/lang/en/messages.php create mode 100644 workbench/lang/fil-PH/messages.php create mode 100644 workbench/resources/views/locale-email.blade.php create mode 100644 workbench/storage/app/public/test.txt diff --git a/.gitignore b/.gitignore index f11b2a6..b730e63 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ vendor/ composer.lock .phpunit.result.cache .phpunit.cache +test + diff --git a/README.md b/README.md index 0dc8cdf..9471348 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,7 @@ We feel the package is currently feature complete, but feel free to send a pull # Requirements -This package requires Laravel 6.0 or higher. - -Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions. +This package requires Laravel 10 or 11. # Installation @@ -34,7 +32,8 @@ composer require stackkit/laravel-database-emails Publish the configuration files. ```bash -php artisan vendor:publish --tag=laravel-database-emails-config +php artisan vendor:publish --tag=database-emails-config +php artisan vendor:publish --tag=database-emails-migrations ``` Create the database table required for this package. @@ -65,68 +64,89 @@ protected function schedule(Schedule $schedule) ### Send an email +E-mails are composed the same way mailables are created. + ```php label('welcome') - ->recipient('john@doe.com') - ->subject('This is a test') - ->view('emails.welcome') - ->variables([ - 'name' => 'John Doe', + ->content(fn (Content $content) => $content + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->envelope(fn (Envelope $envelope) => $envelope + ->subject('Hello') + ->from('johndoe@example.com', 'John Doe') + ->to('janedoe@example.com', 'Jane Doe') + ) + ->attachments([ + Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'), ]) ->send(); +]) ``` -### Specify multiple recipients +### Sending emails to users in your application ```php recipient([ - 'john@doe.com', - 'jane@doe.com' - ]); + ->user($user) + ->send(); ``` -### CC and BCC +By default, the `name` column will be used to set the recipient's name. If you wish to use another column, you should implement the `preferredEmailName` method in your model. ```php cc('john@doe.com') - ->cc(['john@doe.com', 'jane@doe.com']) - ->bcc('john@doe.com') - ->bcc(['john@doe.com', 'jane@doe.com']); +class User extends Model +{ + public function preferredEmailName(): string + { + return $this->first_name; + } +} ``` -### Reply-To +By default, the `email` column will be used to set the recipient's e-mail address. If you wish to use another column, you should implement the `preferredEmailAddress` method in your model. ```php replyTo(['john@doe.com', 'jane@doe.com']); +class User extends Model +{ + public function preferredEmailAddress(): string + { + return $this->work_email; + } +} +``` -Email::compose() - ->replyTo(new Address('john@doe.com', 'John Doe')); +By default, the app locale will be used to set the recipient's locale. If you wish to use another column, you should implement the `preferredEmailLocale` method in your model. -Email::compose() - ->replyTo([ - new Address('john@doe.com', 'John Doe'), - new Address('jane@doe.com', 'Jane Doe'), - ]); +```php +locale; + } +} ``` ### Using mailables @@ -145,36 +165,28 @@ Email::compose() ### Attachments -```php -attach('/path/to/file'); -``` +To start attaching files to your e-mails, you may use the `attach` method like you normally would in Laravel. +However, you will have to use this package's `Attachment` class. -Or for in-memory attachments: ```php attachData('

Your order has shipped!

', 'order.html'); + ->attachments([ + Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), + Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + Attachment::fromStorageDisk('my-custom-disk', 'test.txt'), + ]) + ->send(); ``` -### Custom Sender - -```php -from('john@doe.com', 'John Doe'); -``` + +Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. + ### Scheduling @@ -189,27 +201,6 @@ Email::compose() ->later('+2 hours'); ``` -### Encryption (Optional) - -If you wish to encrypt your e-mails, please enable the `encrypt` option in the configuration file. This is disabled by default. Encryption and decryption will be handled by Laravel's built-in encryption mechanism. Please note that by encrypting the e-mail it takes more disk space. - -```text -Without encryption - -7 bytes (label) -16 bytes (recipient) -20 bytes (subject) -48 bytes (view name) -116 bytes (variables) -1874 bytes (e-mail content) -4 bytes (attempts, sending, failed, encrypted) -57 bytes (created_at, updated_at, deleted_at) -... x 10.000 rows = ± 21.55 MB - -With encryption the table size is ± 50.58 MB. -``` - - ### Queueing e-mails **Important**: When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. Please make sure it is removed from `app/Console/Kernel.php`. @@ -235,22 +226,41 @@ Email::compose() ->queue(null, null, now()->addMinutes(10)); ``` -### Test mode (Optional) +If you need more flexibility over how to queued mails are retried, please implement your own email job. + +Within the job you can send the mail like this: + +```php +use Stackkit\LaravelDatabaseEmails\Sender; + +(new Sender)->send($email); +``` + +### Test mode When enabled, all newly created e-mails will be sent to the specified test e-mail address. This is turned off by default. +``` +DATABASE_EMAILS_TESTING_ENABLED=true +DATABASE_EMAILS_TESTING_EMAIL=your-test-recipient@example.com +``` + ### E-mails to send per minute -To configure how many e-mails should be sent each command, please check the `limit` option. The default is `20` e-mails every command. +To configure how many e-mails should be sent each command. + +``` +DATABASE_EMAILS_LIMIT=20 +``` -### Send e-mails immediately (Optional) +### Send e-mails immediately Useful during development when Laravel Scheduler is not running To enable, set the following environment variable: ``` -LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY=true +DATABASE_EMAILS_IMMEDIATELY=true ``` ### Pruning models diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..0fb32bb --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,37 @@ +# From 6.x to 7.x + +## Database changes (Impact: high) + +The way addresses are stored in the database has changed. Therefore, emails created in 6.x and below are incompatible. + +When you upgrade, the existing database table will be renamed to "emails_old" and a new table will be created. + +## Creating emails (Impact: high) + +The way emails are composed has changed and now borrows a lot from Laravel's mailable. + +```php +use Illuminate\Mail\Mailables\Content; +use Stackkit\LaravelDatabaseEmails\Attachment;use Stackkit\LaravelDatabaseEmails\Email; +use Illuminate\Mail\Mailables\Envelope; + +Email::compose() + ->content(fn (Content $content) => $content + ->view('tests::dummy') + ->with(['name' => 'John Doe']) + ) + ->envelope(fn (Envelope $envelope) => $envelope + ->subject('Hello') + ->from('johndoe@example.com', 'John Doe') + ->to('janedoe@example.com', 'Jane Doe') + ) + ->attachments([ + Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'), + ]) + ->send(); +]) +``` + +## Encryption (Impact: moderate) + +E-mail encryption has been removed from the package. diff --git a/config/laravel-database-emails.php b/config/database-emails.php similarity index 61% rename from config/laravel-database-emails.php rename to config/database-emails.php index 6c179ef..6f9d433 100644 --- a/config/laravel-database-emails.php +++ b/config/database-emails.php @@ -15,18 +15,6 @@ 'attempts' => 3, - /* - |-------------------------------------------------------------------------- - | Encryption - |-------------------------------------------------------------------------- - | - | Here you may enable encryption for all e-mails. The e-mail will be encrypted according - | your application's configuration (OpenSSL AES-256-CBC by default). - | - */ - - 'encrypt' => false, - /* |-------------------------------------------------------------------------- | Test E-mail @@ -40,9 +28,9 @@ 'testing' => [ - 'email' => 'test@email.com', + 'email' => env('DATABASE_EMAILS_TESTING_EMAIL'), - 'enabled' => env('LARAVEL_DATABASE_EMAILS_TESTING_ENABLED', true), + 'enabled' => env('DATABASE_EMAILS_TESTING_ENABLED', false), ], @@ -57,7 +45,7 @@ | */ - 'limit' => 20, + 'limit' => env('DATABASE_EMAILS_LIMIT', 20), /* |-------------------------------------------------------------------------- @@ -70,18 +58,5 @@ | */ - 'immediately' => env('LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY', false), - - /* - |-------------------------------------------------------------------------- - | Manual migrations - |-------------------------------------------------------------------------- - | - | This option allows you to use: - | `php artisan vendor:publish --tag=laravel-database-emails-migrations` to push migrations - | to your app's folder, so you're free to modify before migrating. - | - */ - - 'manual_migrations' => (bool) env('LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS', false), + 'immediately' => env('DATABASE_EMAILS_IMMEDIATELY', false), ]; diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_change_binary_to_text.php index 6a196ca..9b18e51 100644 --- a/database/migrations/2024_03_16_151608_change_binary_to_text.php +++ b/database/migrations/2024_03_16_151608_change_binary_to_text.php @@ -13,16 +13,31 @@ class ChangeBinaryToText extends Migration */ public function up() { - Schema::table('emails', function (Blueprint $table) { - $table->text('recipient')->change(); - $table->text('cc')->nullable()->change(); - $table->text('bcc')->nullable()->change(); - $table->text('subject')->change(); - $table->text('variables')->nullable()->change(); - $table->text('body')->change(); - $table->text('attachments')->nullable()->change(); - $table->text('from')->nullable()->change(); - $table->text('reply_to')->nullable()->change(); + Schema::rename('emails', 'emails_old'); + + Schema::create('emails', function (Blueprint $table) { + $table->increments('id'); + $table->string('label')->nullable(); + $table->json('recipient'); + $table->json('cc')->nullable(); + $table->json('bcc')->nullable(); + $table->string('subject'); + $table->string('view'); + $table->json('variables')->nullable(); + $table->text('body'); + $table->integer('attempts')->default(0); + $table->boolean('sending')->default(0); + $table->boolean('failed')->default(0); + $table->text('error')->nullable(); + $table->json('attachments')->nullable(); + $table->json('from')->nullable(); + $table->json('reply_to')->nullable(); + $table->timestamp('queued_at')->nullable(); + $table->timestamp('scheduled_at')->nullable(); + $table->timestamp('sent_at')->nullable()->index(); + $table->timestamp('delivered_at')->nullable(); + $table->timestamps(); + $table->softDeletes(); }); } diff --git a/src/Attachment.php b/src/Attachment.php index 48b2220..dc4d016 100644 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -17,34 +17,34 @@ public function __construct(public string $path, public ?string $disk = null) // } - public static function fromPath($path) + public static function fromPath(string $path): self { return new static($path); } - public static function fromData() + public static function fromData(): void { throw new RuntimeException('Raw attachments are not supported in the database email driver.'); } - public static function fromStorage() + public static function fromStorage(): void { throw new RuntimeException('Raw attachments are not supported in the database email driver.'); } - public static function fromStorageDisk($disk, $path) + public static function fromStorageDisk($disk, $path): self { return new static($path, $disk); } - public function as(string $name) + public function as(string $name): self { $this->as = $name; return $this; } - public function withMime(string $mime) + public function withMime(string $mime): self { $this->mime = $mime; diff --git a/src/Config.php b/src/Config.php index 9c6ae83..689eff4 100644 --- a/src/Config.php +++ b/src/Config.php @@ -11,7 +11,7 @@ class Config */ public static function maxAttemptCount(): int { - return max(config('laravel-database-emails.attempts', 1), 3); + return max(config('database-emails.attempts', 1), 3); } /** @@ -19,7 +19,7 @@ public static function maxAttemptCount(): int */ public static function testing(): bool { - return (bool) config('laravel-database-emails.testing.enabled', false); + return (bool) config('database-emails.testing.enabled', false); } /** @@ -27,7 +27,7 @@ public static function testing(): bool */ public static function testEmailAddress(): string { - return config('laravel-database-emails.testing.email'); + return config('database-emails.testing.email'); } /** @@ -35,7 +35,7 @@ public static function testEmailAddress(): string */ public static function cronjobEmailLimit(): int { - return config('laravel-database-emails.limit', 20); + return config('database-emails.limit', 20); } /** @@ -43,6 +43,6 @@ public static function cronjobEmailLimit(): int */ public static function sendImmediately(): bool { - return (bool) config('laravel-database-emails.immediately', false); + return (bool) config('database-emails.immediately', false); } } diff --git a/src/Email.php b/src/Email.php index a07c81c..9bea33d 100644 --- a/src/Email.php +++ b/src/Email.php @@ -12,26 +12,26 @@ use Throwable; /** - * @property $id - * @property $label - * @property $recipient - * @property $from - * @property $cc - * @property $bcc - * @property $reply_to - * @property $subject - * @property $view - * @property $variables - * @property $body - * @property $attachments - * @property $attempts - * @property $sending - * @property $failed - * @property $error - * @property $queued_at - * @property $scheduled_at - * @property $sent_at - * @property $delivered_at + * @property int $id + * @property string $label + * @property array $recipient + * @property array $from + * @property array $cc + * @property array $bcc + * @property array $reply_to + * @property string $subject + * @property string $view + * @property array $variables + * @property string $body + * @property array $attachments + * @property int $attempts + * @property int $sending + * @property int $failed + * @property int $error + * @property ?Carbon $queued_at + * @property ?Carbon $scheduled_at + * @property ?Carbon $sent_at + * @property ?Carbon $delivered_at */ class Email extends Model { @@ -48,65 +48,27 @@ class Email extends Model 'attachments' => 'json', ]; - /** - * The table in which the e-mails are stored. - * - * @var string - */ protected $table = 'emails'; - /** - * The guarded fields. - * - * @var array - */ protected $guarded = []; public static ?Closure $pruneQuery = null; - /** - * Compose a new e-mail. - */ public static function compose(): EmailComposer { return new EmailComposer(new static()); } - /** - * Get the e-mail from address. - */ - public function getFromAddress(): ?string - { - return $this->from['address']; - } - - /** - * Get the e-mail from address. - */ - public function getFromName(): ?string - { - return $this->from['name']; - } - - /** - * Determine if the e-mail is sent. - */ public function isSent(): bool { return ! is_null($this->sent_at); } - /** - * Determine if the e-mail failed to be sent. - */ public function hasFailed(): bool { return $this->failed == 1; } - /** - * Mark the e-mail as sending. - */ public function markAsSending(): void { $this->update([ @@ -115,9 +77,6 @@ public function markAsSending(): void ]); } - /** - * Mark the e-mail as sent. - */ public function markAsSent(): void { $now = Carbon::now()->toDateTimeString(); @@ -130,9 +89,6 @@ public function markAsSent(): void ]); } - /** - * Mark the e-mail as failed. - */ public function markAsFailed(Throwable $exception): void { $this->update([ @@ -142,48 +98,17 @@ public function markAsFailed(Throwable $exception): void ]); } - /** - * Send the e-mail. - */ public function send(): void { (new Sender())->send($this); } - /** - * Retry sending the e-mail. - */ - public function retry(): void - { - $retry = $this->replicate(); - - $retry->fill( - [ - 'id' => null, - 'attempts' => 0, - 'sending' => 0, - 'failed' => 0, - 'error' => null, - 'sent_at' => null, - 'delivered_at' => null, - ] - ); - - $retry->save(); - } - - /** - * @return void - */ - public static function pruneWhen(Closure $closure) + public static function pruneWhen(Closure $closure): void { static::$pruneQuery = $closure; } - /** - * @return Builder - */ - public function prunable() + public function prunable(): Builder { if (static::$pruneQuery) { return (static::$pruneQuery)($this); diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 8c63a92..aa7da9e 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -5,6 +5,8 @@ namespace Stackkit\LaravelDatabaseEmails; use Closure; +use Illuminate\Contracts\Translation\HasLocalePreference; +use Illuminate\Foundation\Auth\User; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; @@ -32,6 +34,8 @@ class EmailComposer public ?array $attachments = null; + public ?string $locale = null; + /** * Create a new EmailComposer instance. */ @@ -79,6 +83,25 @@ public function attachments(null|array|Closure $attachments = null): self return $this; } + public function user(User $user) + { + return $this->envelope(function (Envelope $envelope) use ($user) { + $name = method_exists($user, 'preferredEmailName') + ? $user->preferredEmailName() + : ($user->name ?? null); + + $email = method_exists($user, 'preferredEmailAddress') + ? $user->preferredEmailAddress() + : $user->email; + + if ($user instanceof HasLocalePreference) { + $this->locale = $user->preferredLocale(); + } + + return $envelope->to($email, $name); + }); + } + /** * Get the e-mail that is being composed. */ diff --git a/src/LaravelDatabaseEmailsServiceProvider.php b/src/LaravelDatabaseEmailsServiceProvider.php index a7b898c..30a9ea4 100644 --- a/src/LaravelDatabaseEmailsServiceProvider.php +++ b/src/LaravelDatabaseEmailsServiceProvider.php @@ -27,7 +27,7 @@ private function bootConfig(): void $this->publishes([ $configDir.'laravel-database-emails.php' => config_path('laravel-database-emails.php'), - ], 'laravel-database-emails-config'); + ], 'database-emails-config'); } /** @@ -38,13 +38,9 @@ private function bootDatabase(): void $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR; $migrationsDir = $baseDir.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR; - if ($this->app['config']->get('laravel-database-emails.manual_migrations')) { - $this->publishes([ - $migrationsDir => "{$this->app->databasePath()}/migrations", - ], 'laravel-database-emails-migrations'); - } else { - $this->loadMigrationsFrom([$migrationsDir]); - } + $this->publishes([ + $migrationsDir => "{$this->app->databasePath()}/migrations", + ], 'database-emails-migrations'); } /** diff --git a/src/MailableReader.php b/src/MailableReader.php index 7f92c0d..22ded41 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -6,14 +6,15 @@ use Error; use Exception; -use Illuminate\Container\Container; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; -use ReflectionObject; +use Illuminate\Support\Traits\Localizable; class MailableReader { + use Localizable; + /** * Read the mailable and pass the data to the email composer. */ @@ -44,14 +45,10 @@ public function attachments(): array }); } - if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) { - $reflected = (new ReflectionObject($composer->getData('mailable'))); - $method = $reflected->getMethod('prepareMailableForDelivery'); - $method->setAccessible(true); - $method->invoke($composer->getData('mailable')); - } else { - Container::getInstance()->call([$composer->getData('mailable'), 'build']); - } + (fn (Mailable $mailable) => $mailable->prepareMailableForDelivery())->call( + $composer->getData('mailable'), + $composer->getData('mailable'), + ); $this->readRecipient($composer); @@ -87,9 +84,9 @@ private function convertMailableAddresses($from): array */ private function readRecipient(EmailComposer $composer): void { - if (config('laravel-database-emails.testing.enabled')) { + if (config('database-emails.testing.enabled')) { $composer->getEmail()->recipient = [ - config('laravel-database-emails.testing.email') => null, + config('database-emails.testing.email') => null, ]; return; @@ -154,7 +151,13 @@ private function readBody(EmailComposer $composer): void $composer->getEmail()->view = $mailable->view; $composer->getEmail()->variables = $mailable->buildViewData(); - $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(); + + $localeToUse = $composer->locale ?? app()->currentLocale(); + + $this->withLocale( + $localeToUse, + fn () => $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(), + ); } /** diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index e8ed7fc..cf833c7 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -63,6 +63,8 @@ public function handle(): void try { $email->send(); } catch (Throwable $e) { + report($e); + $email->markAsFailed($e); } } diff --git a/src/Sender.php b/src/Sender.php index 7f755c2..94490c7 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -43,7 +43,7 @@ private function buildMessage(Message $message, Email $email): void ->bcc($email->bcc ?: []) ->replyTo($email->reply_to ?: []) ->subject($email->subject) - ->from($email->getFromAddress(), $email->getFromName()) + ->from($email->from['address'], $email->from['name']) ->html($email->body); foreach ($email->attachments as $dbAttachment) { @@ -66,15 +66,4 @@ private function buildMessage(Message $message, Email $email): void $message->attach($attachment); } } - - public function prepAddresses(array $addresses) - { - return $addresses; - $new = []; - foreach ($addresses as $value) { - $new[$value['address']] = $value['name']; - } - - return $new; - } } diff --git a/test b/test index d79a5857e4f08dd52b2c70d86e8b68487b55dbca..4be2b3b604b1eb95ab129e3dc85bf08f6d63bd2d 100644 GIT binary patch literal 49152 zcmeI&-)_@Z90zclBz040G6^B8;>M)gs8xjG&{8I(X{_r-C8(e+VrZL=oaD4MICk)# zLUz}R4RN!F*lX++_5^!`U2PY;-|rkdj+3}s*DI8LEgGET-}(8RkB^UICwosGIuSPx z{Gew?#vSD&MOBqA3`0>Ao&K)S-*BnXN_qH({#SGBXRYeW?cnFd#UGW+mE%(Vd0kul zbMdDu-!I;;{5Jple6Vn>a(q@*ltTal5ST@Q_GL|9Us6v>j@RXHc+Ym+kcHffm>scP z*_F-Rtq1#C#`exvTVETEqQHjXd&4aaW2KR1X_!A;(=`3Y4fTB-*=?7nb$z!xTANSP z??3qR;npw-qY#nEp;yO?_#qFBe&F=%U|_uD1H+CZ-|;#D?@@aV&!@lGb#ECB*KTvS zVI12*=g1DIAmAOR?@(03XuEzpAKU39WfaxUt%q^@1@9EZA3OYYmO7t^_U4H0g`<3a zcQCy}JBoO(A5qw_Ps5fQU4CH4Ze-kS-lXP`U)S*t8%E@K1BzX-%xr%Xp$Bx>I4S*m zBo6{Vpg{g6qFr#(8}y^|RSi2wyc^SgXlOKyt{w5n>G7nYVz`Wzb-C*t^I$rd4j0v& z$`SWP!l`0P!wZVZ(C!?G!#>hJ2>f0~|0?G3bhd!^-2sdIB+NXnYWn?U_2jx7=ZPVE z;kQE;$4-~UoH%xn7cvieq+=R+~} z^JM<=H_U6A+jDdJcN?lA=dtjWOZQPMxj51_ofS5VS%t+KamEL88l;tB1^Ym&-({){aTaoN?vwSI!vu5@F^XCB1?(st0vkyqp zN4j!32hQ{bWN22HRD2HUwRw8q>ib7_(u%aGGa>Q>FfE{ayXr`X{x&YQNOp)%K_e3j`nl0SG_<0uX=z1Rwwb2>kB^R;y*@n`*1s zT4T*S%(~00b?a{P9-{~S-XUjg$D?lt=nkBTr%$cF(Jlsj1 z1&dgD(;qTnE-QW(Z>@>$ueFl++@rb4YG$ddJe#aW zerdDv{olM)QBJBNmXbF`t3>|^ZK^ZFhaqFn$x=i$LCkW<6a_itvz?@lxc^t{e<<`1 z3j`nl0SG_<0uX=z1Rwwb2tWV=Ga^t^OE=W)rvhPB-2c;${;@y+0uX=z1Rwwb2tWV= z5P$##ATYxM3!-nh|DWL_jIKcd0uX=z1Rwwb2tWV=5P$##6b0Y^V-!FD0uX=z1Rwwb z2tWV=5P$##W?ul`|Ihw0Mh_tX0SG_<0uX=z1Rwwb2tWV=`2HVb00Izz00bZa0SG_< z0uX=z1RyZ`0=WO5{bP(CLI45~fB*y_009U<00Izz00i*+e~bYLKmY;|fB*y_009U< z00Izz!0Zd)`~TTL#^@mgAOHafKmY;|fB*y_009U<0N?*(3_t(^5P$##AOHafKmY;| KfB*z$U*K;NdPztC literal 16384 zcmeI2&2G~`5XbGrq^%G&5{Ia_t;Hcxt1320sW{=b1|rg=gcKBUur~HKS@1`?b_zMd zffLWbfp_A-18_v*5g5nhvr&3ODw>tnUpxPq@z3^QMt=FC#$!g#L*dhyJdo~6vMfC& zM3N*GrUG+3*5P3GTplm1*sIc>_;v>jvbTRpYFk}{9S$Ub1dsp{Kmter2_OL^fCT;p zfzSEuT7GLw{<0C%j>ni!xfi8d<)~?yEsM0w!i)rX(pH7*^%3$?;^VJpeD8>1|9>4Eu3cOzLbT=<3Qk&;Ce@NEBb!vz=py&=rX&*1E{E>8DPikLu}kq zB%T<9rC}Qfwq7p5u&L|XHldDS=EMOD3I&Ma^DTktoE`ACBS} zwhn=GeH16@Yb@GVGKv&V?DcYT6J;Z{pXrk<%8IfgyTlTK zKmter2_OL^fCP{L5assertEquals(3, Config::maxAttemptCount()); - $this->app['config']->set('laravel-database-emails.attempts', 5); + $this->app['config']->set('database-emails.attempts', 5); $this->assertEquals(5, Config::maxAttemptCount()); } @@ -22,7 +22,7 @@ public function test_testing() { $this->assertFalse(Config::testing()); - $this->app['config']->set('laravel-database-emails.testing.enabled', true); + $this->app['config']->set('database-emails.testing.enabled', true); $this->assertTrue(Config::testing()); } @@ -32,7 +32,7 @@ public function test_test_email_address() { $this->assertEquals('test@email.com', Config::testEmailAddress()); - $this->app['config']->set('laravel-database-emails.testing.email', 'test+update@email.com'); + $this->app['config']->set('database-emails.testing.email', 'test+update@email.com'); $this->assertEquals('test+update@email.com', Config::testEmailAddress()); } @@ -42,7 +42,7 @@ public function test_cronjob_email_limit() { $this->assertEquals(20, Config::cronjobEmailLimit()); - $this->app['config']->set('laravel-database-emails.limit', 15); + $this->app['config']->set('database-emails.limit', 15); $this->assertEquals(15, Config::cronjobEmailLimit()); } diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 4ce0e36..b8ea143 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -102,16 +102,16 @@ public function from_should_be_saved_correctly() { $email = $this->composeEmail()->send(); - $this->assertEquals($email->from['address'], $email->getFromAddress()); - $this->assertEquals($email->from['name'], $email->getFromName()); + $this->assertEquals($email->from['address'], $email->from['address']); + $this->assertEquals($email->from['name'], $email->from['name']); $email = $this->composeEmail([ 'from' => new Address('marick@dolphiq.nl', 'Marick'), ])->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); } #[Test] @@ -165,10 +165,10 @@ public function the_scheduled_date_should_be_saved_correctly() #[Test] public function recipient_should_be_swapped_for_test_address_when_in_testing_mode() { - $this->app['config']->set('laravel-database-emails.testing.enabled', function () { + $this->app['config']->set('database-emails.testing.enabled', function () { return true; }); - $this->app['config']->set('laravel-database-emails.testing.email', 'test@address.com'); + $this->app['config']->set('database-emails.testing.email', 'test@address.com'); $email = $this->sendEmail(['recipient' => 'jane@doe.com']); @@ -182,7 +182,6 @@ public function attachments_should_be_saved_correctly() ->attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), Attachment::fromPath(__DIR__.'/files/pdf-sample2.pdf'), - // Attachment::fromStorage('pdf-sample.pdf'), Attachment::fromStorageDisk('my-custom-disk', 'pdf-sample-2.pdf'), ]) ->send(); diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index d09d0e3..4e29bc1 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -6,7 +6,12 @@ use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\Email; +use Workbench\App\Models\User; +use Workbench\App\Models\UserWithPreferredEmail; +use Workbench\App\Models\UserWithPreferredLocale; +use Workbench\App\Models\UserWithPreferredName; class EnvelopeTest extends TestCase { @@ -31,4 +36,99 @@ public function test_it_can_set_the_envelope() 'janedoe@example.com' => null, ], $email->recipient); } + + #[Test] + public function test_it_can_pass_user_models() + { + $user = (new User())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'johndoe@example.com' => 'J. Doe', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_email() + { + $user = (new UserWithPreferredEmail())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'noreply@abc.com' => 'J. Doe', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_name() + { + $user = (new UserWithPreferredName())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $email = Email::compose() + ->user($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals( + [ + 'johndoe@example.com' => 'J.D.', + ], + $email->recipient + ); + } + + #[Test] + public function users_can_have_a_preferred_locale() + { + $nonLocaleUser = (new User())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $emailForNonLocaleUser = Email::compose() + ->user($nonLocaleUser) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('locale-email')) + ->send(); + + $localeUser = (new UserWithPreferredLocale())->forceFill([ + 'email' => 'johndoe@example.com', + 'name' => 'J. Doe', + ]); + + $emailForLocaleUser = Email::compose() + ->user($localeUser) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('locale-email')) + ->send(); + + $this->assertStringContainsString('Hello!', $emailForNonLocaleUser->body); + $this->assertStringContainsString('Kumusta!', $emailForLocaleUser->body); + } } diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 3129df8..8e89065 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -96,8 +96,8 @@ public function it_extracts_the_from_address_and_or_name() )->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) @@ -105,16 +105,16 @@ public function it_extracts_the_from_address_and_or_name() )->send(); $this->assertTrue((bool) $email->from); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals(null, $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals(null, $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) ->from('marick@dolphiq.nl', 'Marick') )->send(); - $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress()); - $this->assertEquals('Marick', $email->getFromName()); + $this->assertEquals('marick@dolphiq.nl', $email->from['address']); + $this->assertEquals('Marick', $email->from['name']); } } diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index 43b75f1..ae447ed 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -79,7 +79,7 @@ public function the_number_of_emails_sent_per_minute_should_be_limited() $this->sendEmail(); } - $this->app['config']['laravel-database-emails.limit'] = 25; + $this->app['config']['database-emails.limit'] = 25; $this->artisan('email:send'); diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 159295d..7083b01 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -159,15 +159,17 @@ public function attachments_are_added_to_the_email() ->attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'), + Attachment::fromStorageDisk('my-custom-disk', 'test.txt'), ]) ->send(); $this->artisan('email:send'); $attachments = reset($this->sent)->attachments; - $this->assertCount(2, $attachments); + $this->assertCount(3, $attachments); $this->assertEquals('Test123'."\n", $attachments[1]['body']); $this->assertEquals('text/plain disposition: attachment filename: Test123 file', $attachments[1]['disposition']); + $this->assertEquals("my file from public disk\n", $attachments[2]['body']); } #[Test] @@ -186,12 +188,12 @@ public function raw_attachments_are_not_added_to_the_email() #[Test] public function emails_can_be_sent_immediately() { - $this->app['config']->set('laravel-database-emails.immediately', false); + $this->app['config']->set('database-emails.immediately', false); $this->sendEmail(); $this->assertCount(0, $this->sent); Email::truncate(); - $this->app['config']->set('laravel-database-emails.immediately', true); + $this->app['config']->set('database-emails.immediately', true); $this->sendEmail(); $this->assertCount(1, $this->sent); diff --git a/tests/TestCase.php b/tests/TestCase.php index c5f46cc..82ceaa1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Orchestra\Testbench\Concerns\WithWorkbench; use Stackkit\LaravelDatabaseEmails\Email; use Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider; @@ -13,6 +14,7 @@ class TestCase extends \Orchestra\Testbench\TestCase protected $invalid; use LazilyRefreshDatabase; + use WithWorkbench; public function setUp(): void { @@ -52,13 +54,13 @@ protected function getPackageProviders($app) */ protected function getEnvironmentSetUp($app) { - $app['config']->set('laravel-database-emails.attempts', 3); - $app['config']->set('laravel-database-emails.testing.enabled', false); - $app['config']->set('laravel-database-emails.testing.email', 'test@email.com'); + $app['config']->set('database-emails.attempts', 3); + $app['config']->set('database-emails.testing.enabled', false); + $app['config']->set('database-emails.testing.email', 'test@email.com'); $app['config']->set('filesystems.disks.my-custom-disk', [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => __DIR__.'/../workbench/storage/app/public', ]); $app['config']->set('database.default', 'testbench'); diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 0000000..d4ad593 --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,11 @@ +loadTranslationsFrom( + __DIR__.'/../../lang', + 'package' + ); } /** diff --git a/workbench/lang/en/messages.php b/workbench/lang/en/messages.php new file mode 100644 index 0000000..98f8a0b --- /dev/null +++ b/workbench/lang/en/messages.php @@ -0,0 +1,5 @@ + 'Hello!', +]; diff --git a/workbench/lang/fil-PH/messages.php b/workbench/lang/fil-PH/messages.php new file mode 100644 index 0000000..f801763 --- /dev/null +++ b/workbench/lang/fil-PH/messages.php @@ -0,0 +1,5 @@ + 'Kumusta!', +]; diff --git a/workbench/resources/views/locale-email.blade.php b/workbench/resources/views/locale-email.blade.php new file mode 100644 index 0000000..6d1b8a4 --- /dev/null +++ b/workbench/resources/views/locale-email.blade.php @@ -0,0 +1,2 @@ +{{ trans('workbench::messages.greeting') }} + diff --git a/workbench/storage/app/public/test.txt b/workbench/storage/app/public/test.txt new file mode 100644 index 0000000..77c2f3e --- /dev/null +++ b/workbench/storage/app/public/test.txt @@ -0,0 +1 @@ +my file from public disk From 75073f3a971b174d401b42e875146cce5ffeda35 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 00:57:55 +0100 Subject: [PATCH 42/60] wip --- README.md | 22 +++-- ...=> 2024_03_16_151608_create_new_table.php} | 3 +- logo.png | Bin 16182 -> 0 bytes src/Email.php | 10 +- src/EmailComposer.php | 65 ++----------- src/HasEncryptedAttributes.php | 92 ------------------ src/MailableReader.php | 9 ++ src/Sender.php | 11 +-- test | Bin 49152 -> 53248 bytes tests/ComposeTest.php | 53 ++++++++++ 10 files changed, 92 insertions(+), 173 deletions(-) rename database/migrations/{2024_03_16_151608_change_binary_to_text.php => 2024_03_16_151608_create_new_table.php} (94%) delete mode 100644 logo.png delete mode 100644 src/HasEncryptedAttributes.php create mode 100644 tests/ComposeTest.php diff --git a/README.md b/README.md index 9471348..3bf00f6 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,13 @@ -

- -

Build Status Latest Stable Version License

-# Package Documentation +# Introduction This package allows you to store and send e-mails using a database. -## Contribution - -The package is MIT licenced, meaning it's open source and you are free to copy or fork it and modify it any way you wish. - -We feel the package is currently feature complete, but feel free to send a pull request or help improve existing code. - # Requirements This package requires Laravel 10 or 11. @@ -188,6 +179,17 @@ Email::compose() Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. +### Attaching models to e-mails + +You may attach models to your e-mails. + +```php + +Email::compose() + ->model(User::find(1)); + +``` + ### Scheduling You may schedule an e-mail by calling `later` instead of `send`. You must provide a Carbon instance or a strtotime valid date. diff --git a/database/migrations/2024_03_16_151608_change_binary_to_text.php b/database/migrations/2024_03_16_151608_create_new_table.php similarity index 94% rename from database/migrations/2024_03_16_151608_change_binary_to_text.php rename to database/migrations/2024_03_16_151608_create_new_table.php index 9b18e51..21bafee 100644 --- a/database/migrations/2024_03_16_151608_change_binary_to_text.php +++ b/database/migrations/2024_03_16_151608_create_new_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class ChangeBinaryToText extends Migration +class CreateNewTable extends Migration { /** * Run the migrations. @@ -31,6 +31,7 @@ public function up() $table->text('error')->nullable(); $table->json('attachments')->nullable(); $table->json('from')->nullable(); + $table->nullableMorphs('model'); $table->json('reply_to')->nullable(); $table->timestamp('queued_at')->nullable(); $table->timestamp('scheduled_at')->nullable(); diff --git a/logo.png b/logo.png deleted file mode 100644 index 69d1e49188ea12e0d97dac9e62c326c969c57636..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16182 zcmeI3c~}#78pnrK#O3g66|`1jtS5w-9E4;x36&!TYXD&5AfH0C>DYzFmNi z@_Yb5d{?iIwZ$rDz$k7EKs0zF7LaZ<6SDz85}a;E&}7WUO2o8!la$@|%~3W>uaUCn z1S%n=ITX|B6`2-nR%WCc%}hqc8g{VEU6KwH0gRXpVWk@lCM%pSW!vMz#BXvKWV7s3 zY{^pgR5BqeRvFC-#Vr^sFhIybxqJ~TNF2Zo6pMHQe-;no3PDH=as?cS3qxWU;<7ql zY?(XpOJdO^!KyHMXF9@?vUN6_83w_$w6uUUegJOKf?Tm!3_?7R$Kwz)IMxi44N2#i ztm7O(y5fXkR@9<5+w{1JMaD%E@f4es%_b9dzdEnWXzosAvUai~D1zyT8RP~)U~eJ~ z+O09CSPb@4)1V+`z>Jv5W+imoUUg<2Zo{oQypNRK?miO}Yob(k=kE1n*>tqQ?74@umPdqFPv0*Y&%YlR( zo=DB(!hu|vC-j4OFa$XTam=K|P~sYWQbunvc(8!)9K&fQH3m^Z8pMXYR21jAPH{9S zoP=A9h)t$9B3cYIo3s+JXV7V8_r8KdaRY84P79OqC17vEPAdCegCk5<8)8DSh%gy3 z5TMsi%hh>VIkTy7A;LAaQJg9(Emo)98hjF8ur$SK$hiJ~leqVpjJ zr#a-cXvk|3Vj?jQ)o=uS0m>0zL<$jC!{dlVNth7gBSDZ@)Rm%_FfXK1=&i(l&2ZYs z#BR3twprN1=SRC72EDyUnGuT>BP&qK?kd6Vrq)DEq9wgIMt-TN+})rfP{D@^ZEBi2J;dnm_`Q z{|5&B|6nPsNa`P4h?lCXKe!OiJ?j6t5Is8?)gdM=rjdbNo49M><<`!zqR&@XSH-?q zjS})N3MML<*j0$Vf{0f2!dOpc$NX5bA`L0_`9VC0c!6B#iq5z>c8C>*yC|(8^1qi*#;G*Y4@iMq5KUIrHh2))VRqUS^LGPo!}=uHL}Js*mf!9@W=Z!);(`B1zJE(#EOlfgyLhvH>$ zQGn2!w7A@R9^}MK#M7H;#ABLSt3K>cJmSeh6)GhFWR3@bWh(&S;WOgSpa)X&VW6Pr^9EW z7X15yfg4U(MN#~yFGeqYe9t$C>D>y>MK*PZ^- z<#z0q1YlTv_Px>8aXIl2ux00mIiKz7 z@(4B?8-QIie5Y+J!Bj$dO?*K6&x*@?RKi08M!L0>s%vDMN^W|T-8gX3JMngTjc(K3 zxf`C2nR7kdzw&_3%AkVnWy6DipB585;2tWS{b-Q&%Yq93^_G+|U%d6ONcB@(qjJ;T z?{}9isPXfvF77B)JiK38zs2?G@z}aGnb^fCRX!QVeSGE7!NLyF>w{lSZOFP4r&$}W%=Ocha#ig^k3AlD{#xjzI^QOv zYTi=L!HJufi0`(JJn+Mwg~OAYYX@LEmunN_XYM|myL#~UrkE!k%h<-_;!=+XP0@M2 zDJNFsdWUYCt5Z~e?YpXf+k^IhoW8k!R+gKmJ}IxY>cq@t{>L(0PM$cTU02_q^PULm^RB zO|Dl*uex$V;ThhE#gxMcIvSK4wm-Muh#!ysIpBlkPDBgXrV%=??<(s##4W?i4q zv}4q@49+R}P+KU!?2~vP^Av2zzJK`h?-sS4RC+Bt>KF3!7R9#H&7xwH2Cr_>N%tr& zkC?nYu9368WTEPC>r&my`stTrC(oPTcy?W2yn0_=v3ws`wRd?fUiHS+`JdMMPTHG! z;$PK5_n&@c;Q5kg+v58{_I4+fK{p zz9)~_6BqYM@^+tl<;NnMQI)H&+7MNDz~AR+V^MCI=i#Fp)sXx_oA-sk6qP3~+8fq( z?VX%&Inx%6*lNmI_+#sc=*8PwSA}RtoDateTimeString(); - $this->update([ 'sending' => 0, - 'sent_at' => $now, + 'sent_at' => now(), 'failed' => 0, 'error' => '', ]); @@ -116,4 +115,9 @@ public function prunable(): Builder return $this->where('created_at', '<', now()->subMonths(6)); } + + public function model(): MorphTo + { + return $this->morphTo(); + } } diff --git a/src/EmailComposer.php b/src/EmailComposer.php index aa7da9e..c62b7fc 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Contracts\Translation\HasLocalePreference; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; @@ -102,6 +103,13 @@ public function user(User $user) }); } + public function model(Model $model) + { + $this->setData('model', $model); + + return $this; + } + /** * Get the e-mail that is being composed. */ @@ -110,11 +118,6 @@ public function getEmail(): Email return $this->email; } - /** - * Set a data value. - * - * @param mixed $value - */ public function setData(string $key, $value): self { $this->data[$key] = $value; @@ -122,12 +125,6 @@ public function setData(string $key, $value): self return $this; } - /** - * Get a data value. - * - * @param mixed $default - * @return mixed - */ public function getData(string $key, $default = null) { if (! is_null($default) && ! $this->hasData($key)) { @@ -137,17 +134,11 @@ public function getData(string $key, $default = null) return $this->data[$key]; } - /** - * Determine if the given data value was set. - */ public function hasData(string $key): bool { return isset($this->data[$key]); } - /** - * Set the e-mail label. - */ public function label(string $label): self { $this->email->label = $label; @@ -155,11 +146,6 @@ public function label(string $label): self return $this; } - /** - * Schedule the e-mail. - * - * @param mixed $scheduledAt - */ public function later($scheduledAt): Email { $this->email->scheduled_at = Carbon::parse($scheduledAt); @@ -167,11 +153,6 @@ public function later($scheduledAt): Email return $this->send(); } - /** - * Queue the e-mail. - * - * @param \DateTimeInterface|\DateInterval|int|null $delay - */ public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email { $connection = $connection ?: config('queue.default'); @@ -187,9 +168,6 @@ public function queue(?string $connection = null, ?string $queue = null, $delay return $this->send(); } - /** - * Set the Mailable. - */ public function mailable(Mailable $mailable): self { $this->setData('mailable', $mailable); @@ -199,33 +177,6 @@ public function mailable(Mailable $mailable): self return $this; } - /** - * Attach a file to the e-mail. - */ - // public function attach(string $file, array $options = []): self - // { - // $attachments = $this->hasData('attachments') ? $this->getData('attachments') : []; - // - // $attachments[] = compact('file', 'options'); - // - // return $this->setData('attachments', $attachments); - // } - // - // /** - // * Attach in-memory data as an attachment. - // */ - // public function attachData(string $data, string $name, array $options = []): self - // { - // $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : []; - // - // $attachments[] = compact('data', 'name', 'options'); - // - // return $this->setData('rawAttachments', $attachments); - // } - - /** - * Send the e-mail. - */ public function send(): Email { if ($this->envelope && $this->content) { diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php deleted file mode 100644 index 0c23f2d..0000000 --- a/src/HasEncryptedAttributes.php +++ /dev/null @@ -1,92 +0,0 @@ -attributes[$key]; - - if (is_resource($value)) { - $value = stream_get_contents($value); - } - - if ($this->isEncrypted() && in_array($key, $this->encrypted)) { - try { - $value = decrypt($value); - } catch (DecryptException $e) { - $value = ''; - } - } - - if (in_array($key, $this->encoded) && is_string($value)) { - $decoded = json_decode($value, true); - - if (! is_null($decoded)) { - $value = $decoded; - } - } - - // BC fix for attachments in 4.1.0 and lower. - // Attachments were stored json encoded. - // Because this doesn't work for raw attachments, value is now serialized. - // Check if value is json encoded or serialized, and decode or unserialize accordingly. - if ($key == 'attachments') { - if (substr($value, 0, 2) === 'a:') { - $unserialized = @unserialize($value); - if ($value !== false) { - $value = $unserialized; - } - } else { - $decoded = json_decode($value, true); - - if (! is_null($decoded)) { - $value = $decoded; - } - } - } - - return $value; - } -} diff --git a/src/MailableReader.php b/src/MailableReader.php index 22ded41..9dc176c 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -65,6 +65,8 @@ public function attachments(): array $this->readBody($composer); $this->readAttachments($composer); + + $this->readModel($composer); } /** @@ -176,6 +178,13 @@ private function readAttachments(EmailComposer $composer): void }, $mailable->attachments); } + public function readModel(EmailComposer $composer): void + { + if ($composer->hasData('model')) { + $composer->getEmail()->model()->associate($composer->getData('model')); + } + } + private function prepareAddressForDatabaseStorage(array $addresses): array { return collect($addresses)->mapWithKeys(function ($recipient) { diff --git a/src/Sender.php b/src/Sender.php index 94490c7..736c830 100644 --- a/src/Sender.php +++ b/src/Sender.php @@ -10,9 +10,6 @@ class Sender { - /** - * Send the given e-mail. - */ public function send(Email $email): void { if ($email->isSent()) { @@ -25,17 +22,11 @@ public function send(Email $email): void $this->buildMessage($message, $email); }); - // $sentMessage is null when mocking (Mail::shouldReceive('send')->once()) - if (! is_null($sentMessage)) { - event(new MessageSent($sentMessage)); - } + event(new MessageSent($sentMessage)); $email->markAsSent(); } - /** - * Build the e-mail message. - */ private function buildMessage(Message $message, Email $email): void { $message->to($email->recipient) diff --git a/test b/test index 4be2b3b604b1eb95ab129e3dc85bf08f6d63bd2d..c940b5b00a3c99efa1bd6dc9f77bda83f3cc97de 100644 GIT binary patch delta 378 zcmZo@U~X8zJV9Def`Ng77l>hif1-}Dssw{x(IQ?hQ3f7nKL-9tesDbxTGXwXYOWst`&^xx%nxnIq@Zx1*u93Wr;<}8Hq(Y z3Q7>s%oHVs%)FA+^wgrsN4Pg{zQtn}g-nE?ndw001eCa(w^* delta 181 zcmZozz}(QlJV9DeoPmLX2Z&*SZ=#N|vN(fY(IQ^nHU@6iGG_ires 'John Doe', + 'email' => 'johndoe@example.com', + 'password' => 'secret', + ]); + + $email = Email::compose() + ->user($user) + ->model($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertEquals($email->model_type, $user->getMorphClass()); + $this->assertEquals($email->model_id, $user->getKey()); + } + + #[Test] + public function models_can_be_empty(): void + { + $user = User::forceCreate([ + 'name' => 'John Doe', + 'email' => 'johndoe@example.com', + 'password' => 'secret', + ]); + + $email = Email::compose() + ->model($user) + ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) + ->content(fn (Content $content) => $content->view('welcome')) + ->send(); + + $this->assertNull($email->model_type); + $this->assertNull($email->model_id); + } +} From dd232d88ed05f0a02ba4777f0d1308bde01df809 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 01:03:34 +0100 Subject: [PATCH 43/60] wip --- .github/workflows/run-tests.yml | 2 +- tests/ComposeTest.php | 2 +- tests/DatabaseInteractionTest.php | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 184589d..6790273 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,7 +62,7 @@ jobs: needs: access_check strategy: matrix: - db: [ 'mysql', 'sqlite' ] + db: [ 'mysql', 'sqlite', 'pgsql' ] payload: - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } diff --git a/tests/ComposeTest.php b/tests/ComposeTest.php index 7ffeb93..49e88b9 100644 --- a/tests/ComposeTest.php +++ b/tests/ComposeTest.php @@ -42,7 +42,7 @@ public function models_can_be_empty(): void ]); $email = Email::compose() - ->model($user) + ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) ->content(fn (Content $content) => $content->view('welcome')) ->send(); diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index b8ea143..11fdb6d 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -119,7 +119,6 @@ public function variables_should_be_saved_correctly() { $email = $this->sendEmail(['variables' => ['name' => 'John Doe']]); - $this->assertEquals(json_encode(['name' => 'John Doe'], 1), DB::table('emails')->find(1)->variables); $this->assertEquals(['name' => 'John Doe'], $email->variables); } From af45f98b5f999756d429b7ab2b99566e740c36ca Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 01:05:21 +0100 Subject: [PATCH 44/60] Test package with PostgreSQL --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 184589d..6790273 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -62,7 +62,7 @@ jobs: needs: access_check strategy: matrix: - db: [ 'mysql', 'sqlite' ] + db: [ 'mysql', 'sqlite', 'pgsql' ] payload: - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } From 8e87ad09b60c541931fbba553f482eb7f4ab427f Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 12:37:05 +0100 Subject: [PATCH 45/60] wip --- README.md | 121 +++++++----------- UPGRADING.md | 51 +++++++- config/database-emails.php | 10 +- .../2017_12_14_151403_create_emails_table.php | 52 -------- ...151421_add_attachments_to_emails_table.php | 34 ----- ..._12_22_114011_add_from_to_emails_table.php | 34 ----- ...02_21000_add_queued_at_to_emails_table.php | 34 ----- ...28_140000_add_reply_to_to_emails_table.php | 30 ----- .../2024_03_16_151608_create_new_table.php | 4 +- src/EmailComposer.php | 95 +++++--------- src/MailableReader.php | 52 ++++---- src/SendEmailJob.php | 2 +- src/SendEmailsCommand.php | 49 ++----- src/Store.php | 7 +- test | Bin 53248 -> 0 bytes tests/MailableReaderTest.php | 18 +-- tests/QueuedEmailsTest.php | 11 ++ tests/TestCase.php | 4 +- workbench/app/Jobs/CustomSendEmailJob.php | 11 ++ 19 files changed, 207 insertions(+), 412 deletions(-) delete mode 100644 database/migrations/2017_12_14_151403_create_emails_table.php delete mode 100644 database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php delete mode 100644 database/migrations/2017_12_22_114011_add_from_to_emails_table.php delete mode 100644 database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php delete mode 100644 database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php delete mode 100644 test create mode 100644 workbench/app/Jobs/CustomSendEmailJob.php diff --git a/README.md b/README.md index 3bf00f6..120cc93 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Introduction -This package allows you to store and send e-mails using a database. +This package allows you to store and send e-mails using the database. # Requirements @@ -16,34 +16,26 @@ This package requires Laravel 10 or 11. Require the package using composer. -```bash +```shell composer require stackkit/laravel-database-emails ``` Publish the configuration files. -```bash +```shell php artisan vendor:publish --tag=database-emails-config php artisan vendor:publish --tag=database-emails-migrations ``` Create the database table required for this package. -```bash +```shell php artisan migrate ``` Add the e-mail cronjob to your scheduler ```php -command('email:send')->everyMinute()->withoutOverlapping(5); @@ -58,8 +50,6 @@ protected function schedule(Schedule $schedule) E-mails are composed the same way mailables are created. ```php -user($user) ->send(); ``` -By default, the `name` column will be used to set the recipient's name. If you wish to use another column, you should implement the `preferredEmailName` method in your model. +By default, the `name` column will be used to set the recipient's name. If you wish to use something different, you should implement the `preferredEmailName` method in your model. ```php -mailable(new OrderShipped()) ->send(); @@ -156,16 +128,11 @@ Email::compose() ### Attachments -To start attaching files to your e-mails, you may use the `attach` method like you normally would in Laravel. +To start attaching files to your e-mails, you may use the `attachments` method like you normally would in Laravel. However, you will have to use this package's `Attachment` class. ```php -attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), @@ -175,19 +142,16 @@ Email::compose() ->send(); ``` - -Note: `fromData()` and `fromStorage()` are not supported as the work with raw data. - +> [!NOTE] +> `Attachment::fromData()` and `Attachment::fromStorage()` are not supported as they work with raw data. ### Attaching models to e-mails -You may attach models to your e-mails. +You may attach a model to an e-mail. This can be useful to attach a user or another model that belongs to the e-mail. ```php - Email::compose() ->model(User::find(1)); - ``` ### Scheduling @@ -195,64 +159,65 @@ Email::compose() You may schedule an e-mail by calling `later` instead of `send`. You must provide a Carbon instance or a strtotime valid date. ```php -later('+2 hours'); ``` ### Queueing e-mails -**Important**: When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. Please make sure it is removed from `app/Console/Kernel.php`. +> [!IMPORTANT] +> When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. ```php -queue(); -use Stackkit\LaravelDatabaseEmails\Email; +// On a specific connection +Email::compose()->queue(connection: 'sqs'); -Email::compose() - ->queue(); +// On a specific queue +Email::compose()->queue(queue: 'email-queue'); -// on a specific connection -Email::compose() - ->queue('sqs'); +// Delay (send mail in 10 minutes) +Email::compose()->queue(delay: now()->addMinutes(10)); +``` -// on a specific queue -Email::compose() - ->queue(null, 'email-queue'); +If you need more flexibility, you may also pass your own job class: -// timeout (send mail in 10 minutes) -Email::compose() - ->queue(null, null, now()->addMinutes(10)); +```php +Email::compose()->queue(jobClass: CustomSendEmailJob::class); ``` -If you need more flexibility over how to queued mails are retried, please implement your own email job. - -Within the job you can send the mail like this: +It could look like this: ```php -use Stackkit\LaravelDatabaseEmails\Sender; +send($email); +class CustomSendEmailJob extends SendEmailJob implements ShouldQueue +{ + // Define custom retries, backoff, etc... +} ``` ### Test mode When enabled, all newly created e-mails will be sent to the specified test e-mail address. This is turned off by default. -``` -DATABASE_EMAILS_TESTING_ENABLED=true -DATABASE_EMAILS_TESTING_EMAIL=your-test-recipient@example.com +```dotenv +DB_EMAILS_TESTING_ENABLED=true +DB_EMAILS_TESTING_EMAIL=your-test-recipient@example.com ``` ### E-mails to send per minute To configure how many e-mails should be sent each command. -``` -DATABASE_EMAILS_LIMIT=20 +```dotenv +DB_EMAILS_LIMIT=20 ``` ### Send e-mails immediately @@ -261,8 +226,8 @@ Useful during development when Laravel Scheduler is not running To enable, set the following environment variable: -``` -DATABASE_EMAILS_IMMEDIATELY=true +```dotenv +DB_EMAILS_IMMEDIATELY=true ``` ### Pruning models @@ -272,7 +237,7 @@ use Stackkit\LaravelDatabaseEmails\Email; $schedule->command('model:prune', [ '--model' => [Email::class], -])->everyMinute(); +])->daily(); ``` By default, e-mails are pruned when they are older than 6 months. diff --git a/UPGRADING.md b/UPGRADING.md index 0fb32bb..2825a32 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,18 +1,61 @@ # From 6.x to 7.x -## Database changes (Impact: high) +7.x is a bigger change which cleans up parts of the code base and modernizes the package. That means there are a few high-impact changes. + +## Database changes (Impact: High) The way addresses are stored in the database has changed. Therefore, emails created in 6.x and below are incompatible. When you upgrade, the existing database table will be renamed to "emails_old" and a new table will be created. -## Creating emails (Impact: high) +The table migration now needs to be published first. Please run this command: + +```shell +php artisan vendor:publish --tag=database-emails-migrations +``` + +Then, run the migration: + +```shell +php artisan migrate +``` + +## Environment variables, configurations (Impact: High) + +Environment variable names, as well as the config file name, have been shortened. + +Please publish the new configuration file: + +```shell +php artisan vendor:publish --tag=database-emails-config +``` + +You can remove the old configuration file. + +Rename the following environments: + +`LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` `→` `DB_EMAILS_TESTING_ENABLED` +`LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` `->` `DB_EMAILS_SEND_IMMEDIATELY` + +The following environments are new: + +- `DB_EMAILS_ATTEMPTS` +- `DB_EMAILS_TESTING_EMAIL` +- `DB_EMAILS_LIMIT` +- `DB_EMAILS_IMMEDIATELY` + +The following environments have been removed: + +- `LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS` because migrations are always published. + +## Creating emails (Impact: High) The way emails are composed has changed and now borrows a lot from Laravel's mailable. ```php use Illuminate\Mail\Mailables\Content; -use Stackkit\LaravelDatabaseEmails\Attachment;use Stackkit\LaravelDatabaseEmails\Email; +use Stackkit\LaravelDatabaseEmails\Attachment; +use Stackkit\LaravelDatabaseEmails\Email; use Illuminate\Mail\Mailables\Envelope; Email::compose() @@ -32,6 +75,6 @@ Email::compose() ]) ``` -## Encryption (Impact: moderate) +## Encryption (Impact: moderate/low) E-mail encryption has been removed from the package. diff --git a/config/database-emails.php b/config/database-emails.php index 6f9d433..0d85003 100644 --- a/config/database-emails.php +++ b/config/database-emails.php @@ -13,7 +13,7 @@ | */ - 'attempts' => 3, + 'attempts' => env('DB_EMAILS_ATTEMPTS', 3), /* |-------------------------------------------------------------------------- @@ -28,9 +28,9 @@ 'testing' => [ - 'email' => env('DATABASE_EMAILS_TESTING_EMAIL'), + 'email' => env('DB_EMAILS_TESTING_EMAIL'), - 'enabled' => env('DATABASE_EMAILS_TESTING_ENABLED', false), + 'enabled' => env('DB_EMAILS_TESTING_ENABLED', false), ], @@ -45,7 +45,7 @@ | */ - 'limit' => env('DATABASE_EMAILS_LIMIT', 20), + 'limit' => env('DB_EMAILS_LIMIT', 20), /* |-------------------------------------------------------------------------- @@ -58,5 +58,5 @@ | */ - 'immediately' => env('DATABASE_EMAILS_IMMEDIATELY', false), + 'immediately' => env('DB_EMAILS_IMMEDIATELY', false), ]; diff --git a/database/migrations/2017_12_14_151403_create_emails_table.php b/database/migrations/2017_12_14_151403_create_emails_table.php deleted file mode 100644 index 23489da..0000000 --- a/database/migrations/2017_12_14_151403_create_emails_table.php +++ /dev/null @@ -1,52 +0,0 @@ -increments('id'); - $table->string('label')->nullable(); - $table->binary('recipient'); - $table->binary('cc')->nullable(); - $table->binary('bcc')->nullable(); - $table->binary('subject'); - $table->string('view', 255); - $table->binary('variables')->nullable(); - $table->binary('body'); - $table->integer('attempts')->default(0); - $table->boolean('sending')->default(0); - $table->boolean('failed')->default(0); - $table->text('error')->nullable(); - $table->boolean('encrypted')->default(0); - $table->timestamp('scheduled_at')->nullable(); - $table->timestamp('sent_at')->nullable(); - $table->timestamp('delivered_at')->nullable(); - $table->timestamps(); - $table->softDeletes(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php b/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php deleted file mode 100644 index d47724e..0000000 --- a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -binary('attachments')->after('body')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php b/database/migrations/2017_12_22_114011_add_from_to_emails_table.php deleted file mode 100644 index 7d8c2b4..0000000 --- a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -binary('from')->after('body')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php b/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php deleted file mode 100644 index 5414048..0000000 --- a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php +++ /dev/null @@ -1,34 +0,0 @@ -timestamp('queued_at')->after('encrypted')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php b/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php deleted file mode 100644 index 6847ba2..0000000 --- a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php +++ /dev/null @@ -1,30 +0,0 @@ -binary('reply_to')->nullable()->after('bcc'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } -} diff --git a/database/migrations/2024_03_16_151608_create_new_table.php b/database/migrations/2024_03_16_151608_create_new_table.php index 21bafee..5c0424a 100644 --- a/database/migrations/2024_03_16_151608_create_new_table.php +++ b/database/migrations/2024_03_16_151608_create_new_table.php @@ -13,7 +13,9 @@ class CreateNewTable extends Migration */ public function up() { - Schema::rename('emails', 'emails_old'); + if (Schema::hasTable('emails')) { + Schema::rename('emails', 'emails_old'); + } Schema::create('emails', function (Blueprint $table) { $table->increments('id'); diff --git a/src/EmailComposer.php b/src/EmailComposer.php index c62b7fc..fc4d67c 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -15,19 +15,7 @@ class EmailComposer { - /** - * The e-mail that is being composed. - * - * @var Email - */ - private $email; - - /** - * The e-email data. - * - * @var array - */ - protected $data = []; + public ?Mailable $mailable = null; public ?Envelope $envelope = null; @@ -37,12 +25,21 @@ class EmailComposer public ?string $locale = null; - /** - * Create a new EmailComposer instance. - */ - public function __construct(Email $email) + public ?Model $model = null; + + public ?bool $queued = null; + + public ?string $connection = null; + + public ?string $queue = null; + + public int|Carbon|null $delay = null; + + public ?string $jobClass = null; + + public function __construct(public Email $email) { - $this->email = $email; + // } public function envelope(null|Envelope|Closure $envelope = null): self @@ -105,40 +102,11 @@ public function user(User $user) public function model(Model $model) { - $this->setData('model', $model); - - return $this; - } - - /** - * Get the e-mail that is being composed. - */ - public function getEmail(): Email - { - return $this->email; - } - - public function setData(string $key, $value): self - { - $this->data[$key] = $value; + $this->model = $model; return $this; } - public function getData(string $key, $default = null) - { - if (! is_null($default) && ! $this->hasData($key)) { - return $default; - } - - return $this->data[$key]; - } - - public function hasData(string $key): bool - { - return isset($this->data[$key]); - } - public function label(string $label): self { $this->email->label = $label; @@ -153,24 +121,25 @@ public function later($scheduledAt): Email return $this->send(); } - public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email + public function queue(?string $connection = null, ?string $queue = null, $delay = null, ?string $jobClass = null): Email { $connection = $connection ?: config('queue.default'); $queue = $queue ?: 'default'; $this->email->queued_at = now(); - $this->setData('queued', true); - $this->setData('connection', $connection); - $this->setData('queue', $queue); - $this->setData('delay', $delay); + $this->queued = true; + $this->connection = $connection; + $this->queue = $queue; + $this->delay = $delay; + $this->jobClass = $jobClass; return $this->send(); } public function mailable(Mailable $mailable): self { - $this->setData('mailable', $mailable); + $this->mailable = $mailable; (new MailableReader())->read($this); @@ -194,17 +163,21 @@ public function send(): Email $this->email->refresh(); - if ($this->getData('queued', false) === true) { - dispatch(new SendEmailJob($this->email)) - ->onConnection($this->getData('connection')) - ->onQueue($this->getData('queue')) - ->delay($this->getData('delay')); + if (Config::sendImmediately()) { + $this->email->send(); return $this->email; } - if (Config::sendImmediately()) { - $this->email->send(); + if ($this->queued) { + $job = $this->jobClass ?: SendEmailJob::class; + + dispatch(new $job($this->email)) + ->onConnection($this->connection) + ->onQueue($this->queue) + ->delay($this->delay); + + return $this->email; } return $this->email; diff --git a/src/MailableReader.php b/src/MailableReader.php index 9dc176c..bdc3876 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -20,8 +20,14 @@ class MailableReader */ public function read(EmailComposer $composer): void { + $canBeSent = $composer->mailable || ($composer->envelope && $composer->content); + + if (! $canBeSent) { + throw new Error('E-mail cannot be sent: no mailable or envelope and content provided.'); + } + if ($composer->envelope && $composer->content) { - $composer->setData('mailable', new class($composer) extends Mailable + $composer->mailable = new class($composer) extends Mailable { public function __construct(private EmailComposer $composer) { @@ -42,12 +48,12 @@ public function attachments(): array { return $this->composer->attachments ?? []; } - }); + }; } (fn (Mailable $mailable) => $mailable->prepareMailableForDelivery())->call( - $composer->getData('mailable'), - $composer->getData('mailable'), + $composer->mailable, + $composer->mailable, ); $this->readRecipient($composer); @@ -87,15 +93,15 @@ private function convertMailableAddresses($from): array private function readRecipient(EmailComposer $composer): void { if (config('database-emails.testing.enabled')) { - $composer->getEmail()->recipient = [ + $composer->email->recipient = [ config('database-emails.testing.email') => null, ]; return; } - $composer->getEmail()->recipient = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->to); + $composer->email->recipient = $this->prepareAddressForDatabaseStorage( + $composer->mailable->to); } /** @@ -103,7 +109,7 @@ private function readRecipient(EmailComposer $composer): void */ private function readFrom(EmailComposer $composer): void { - $composer->getEmail()->from = head($composer->getData('mailable')->from); + $composer->email->from = head($composer->mailable->from); } /** @@ -111,8 +117,8 @@ private function readFrom(EmailComposer $composer): void */ private function readCc(EmailComposer $composer): void { - $composer->getEmail()->cc = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->cc); + $composer->email->cc = $this->prepareAddressForDatabaseStorage( + $composer->mailable->cc); } /** @@ -120,8 +126,8 @@ private function readCc(EmailComposer $composer): void */ private function readBcc(EmailComposer $composer): void { - $composer->getEmail()->bcc = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->bcc); + $composer->email->bcc = $this->prepareAddressForDatabaseStorage( + $composer->mailable->bcc); } /** @@ -129,8 +135,8 @@ private function readBcc(EmailComposer $composer): void */ private function readReplyTo(EmailComposer $composer): void { - $composer->getEmail()->reply_to = $this->prepareAddressForDatabaseStorage( - $composer->getData('mailable')->replyTo); + $composer->email->reply_to = $this->prepareAddressForDatabaseStorage( + $composer->mailable->replyTo); } /** @@ -138,7 +144,7 @@ private function readReplyTo(EmailComposer $composer): void */ private function readSubject(EmailComposer $composer): void { - $composer->getEmail()->subject = $composer->getData('mailable')->subject; + $composer->email->subject = $composer->mailable->subject; } /** @@ -149,16 +155,16 @@ private function readSubject(EmailComposer $composer): void private function readBody(EmailComposer $composer): void { /** @var Mailable $mailable */ - $mailable = $composer->getData('mailable'); + $mailable = $composer->mailable; - $composer->getEmail()->view = $mailable->view; - $composer->getEmail()->variables = $mailable->buildViewData(); + $composer->email->view = $mailable->view; + $composer->email->variables = $mailable->buildViewData(); $localeToUse = $composer->locale ?? app()->currentLocale(); $this->withLocale( $localeToUse, - fn () => $composer->getEmail()->body = view($mailable->view, $mailable->buildViewData())->render(), + fn () => $composer->email->body = view($mailable->view, $mailable->buildViewData())->render(), ); } @@ -167,9 +173,9 @@ private function readBody(EmailComposer $composer): void */ private function readAttachments(EmailComposer $composer): void { - $mailable = $composer->getData('mailable'); + $mailable = $composer->mailable; - $composer->getEmail()->attachments = array_map(function (array $attachment) { + $composer->email->attachments = array_map(function (array $attachment) { if (! $attachment['file'] instanceof Attachment) { throw new Error('The attachment is not an instance of '.Attachment::class.'.'); } @@ -180,8 +186,8 @@ private function readAttachments(EmailComposer $composer): void public function readModel(EmailComposer $composer): void { - if ($composer->hasData('model')) { - $composer->getEmail()->model()->associate($composer->getData('model')); + if ($composer->model) { + $composer->email->model()->associate($composer->model); } } diff --git a/src/SendEmailJob.php b/src/SendEmailJob.php index a7ba528..a865048 100644 --- a/src/SendEmailJob.php +++ b/src/SendEmailJob.php @@ -26,6 +26,6 @@ public function __construct(Email $email) public function handle(): void { - (new Sender())->send($this->email); + $this->email->send(); } } diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index cf833c7..b1fcb60 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -5,49 +5,19 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Console\Command; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; +use Illuminate\Support\LazyCollection; use Throwable; class SendEmailsCommand extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'email:send'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Send all queued e-mails'; - /** - * The e-mail repository. - * - * @var Store - */ - protected $store; - - /** - * Create a new SendEmailsCommand instance. - */ - public function __construct(Store $store) - { - parent::__construct(); - - $this->store = $store; - } - - /** - * Execute the console command. - */ - public function handle(): void + public function handle(Store $store): void { - $emails = $this->store->getQueue(); + $emails = $store->getQueue(); if ($emails->isEmpty()) { $this->line('There is nothing to send.'); @@ -60,13 +30,10 @@ public function handle(): void foreach ($emails as $email) { $progress->advance(); - try { - $email->send(); - } catch (Throwable $e) { - report($e); - - $email->markAsFailed($e); - } + rescue( + callback: fn () => $email->send(), + rescue: fn (Throwable $e) => $email->markAsFailed($e) + ); } $progress->finish(); @@ -77,7 +44,7 @@ public function handle(): void /** * Output a table with the cronjob result. */ - protected function result(Collection $emails): void + protected function result(LazyCollection $emails): void { $headers = ['ID', 'Recipient', 'Subject', 'Status']; diff --git a/src/Store.php b/src/Store.php index 3a9f4f2..bd26544 100644 --- a/src/Store.php +++ b/src/Store.php @@ -6,6 +6,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\LazyCollection; class Store { @@ -14,11 +15,11 @@ class Store * * @return Collection|Email[] */ - public function getQueue(): Collection + public function getQueue(): LazyCollection { $query = new Email(); - return $query + return Email::query() ->whereNull('deleted_at') ->whereNull('sent_at') ->whereNull('queued_at') @@ -30,6 +31,6 @@ public function getQueue(): Collection ->where('attempts', '<', Config::maxAttemptCount()) ->orderBy('created_at', 'asc') ->limit(Config::cronjobEmailLimit()) - ->get(); + ->cursor(); } } diff --git a/test b/test deleted file mode 100644 index c940b5b00a3c99efa1bd6dc9f77bda83f3cc97de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI&-)_@Z90zclq;=Ca4H7~IaWP}IQ456PP$&p#8e_Sr1QmaZHl$6K6Q5E;Vh8^z zWOu1xh+XU<_8NPIJ;5Gf7t?mJ`~A+b<2a79A+!q?z7Y-1`R8*!=Z}w_Jh*q;jhMFY z2YoxzE=$KHS(ZN6G)a<9(O;eZ#;*n)RL39ae>rzN@9>m#Dfp?rdQw`i>`IMC4W<5P z{m1q1>Nl2tUHNS#sO+x&zV^es%m|0T|4ZOiZB3Yq7)-eW4k^@fyo(=@V*6q4=} zBqOMQPsxJ753(9kUF_}w7o603Kk)lmg1+yto)rxTENe5>ED6o^j8Go*hF0WfS)au$ zW{zbOqhm)ba{G)os(Zkkm?}FHL6s@{O9Ac)AKu7-;xp^i#&`D#kfq5vvFBW|2BhiT%H(| z#RA@^LCI)V4+qp?Z!@0Xm+?eBH8q)1?PM~gAIfJ$z{V4YCo>LfihARmJUZig4ttSI zZ0m{N39UGG9V_-+Dkc}anhw&3yF2$DwzZwzFWX;hdM-gepRW1dR3snCyjnFUD<@0+v~-5Ax@6!i|B zZ{06_aaY1VQ(s~L3lv`0uX=z1Rwwb2tWV=5P-mkDe!5nD&4EKnyoFXdD$|rTILn=YV(?v z+-F-Jdv1wa-PTG~a(OyR)UsOFEOV<#FUxlL!=2<=Fx{;u4>rOykCNswd7dU^Gsju7 zhswn~*OseNOy@DLTT~1FE?!o|!l*+x?g!(V<(}duXS=jim294KThy>+nN&s76gA`z zw2NoDQmsmUmfqarE#GP->2nXoi`k4yRr;!!jp$M^@z5h}|E*(7(x}GMQsGTrE8c!W zn`zAWVaU{TvJ_EH5VJBw5hZ1aXFEwAe*Z5w{*dS&UJ!r)1Rwwb2tWV=5P$##AOHaf zEQr8{TsbReKNSdT;s^f67o>4$1_U4g0SG_<0uX=z1Rwwb2tWV=r2?xw6!-t7HVA|O z1Rwwb2tWV=5P$##AOHafEQ$cW|6i1zL^~h=0SG_<0uX=z1Rwwb2tWV=^9Atz|9l(7 zKmY;|fB*y_009U<00Izz00b6A0N?*FN>8F45P$##AOHafKmY;|fB*y_0D<`exc{GT zgBS=v00Izz00bZa0SG_<0uX?}q6pyk|BKR-Xa@u!009U<00Izz00bZa0SG`~z5u@e zpKpU02tWV=5P$##AOHafKmY;|fWV>%;QRkY=}EK$0uX=z1Rwwb2tWV=5P$##ATVD5 f_y6;45CZ`SKmY;|fB*y_009U<00IzL6oJ11Q?>)l diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 8e89065..7e15865 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -23,16 +23,16 @@ public function it_extracts_the_recipient() $composer = Email::compose() ->mailable($this->mailable()); - $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->getEmail()->recipient); + $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->email->recipient); $composer = Email::compose() ->mailable( $this->mailable()->to(['jane@doe.com']) ); - $this->assertCount(2, $composer->getEmail()->recipient); - $this->assertArrayHasKey('john@doe.com', $composer->getEmail()->recipient); - $this->assertArrayHasKey('jane@doe.com', $composer->getEmail()->recipient); + $this->assertCount(2, $composer->email->recipient); + $this->assertArrayHasKey('john@doe.com', $composer->email->recipient); + $this->assertArrayHasKey('jane@doe.com', $composer->email->recipient); } #[Test] @@ -40,7 +40,7 @@ public function it_extracts_cc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->getEmail()->cc); + $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->email->cc); } #[Test] @@ -48,7 +48,7 @@ public function it_extracts_bcc_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->getEmail()->bcc); + $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->email->bcc); } #[Test] @@ -56,7 +56,7 @@ public function it_extracts_reply_to_addresses() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->getEmail()->reply_to); + $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->email->reply_to); } #[Test] @@ -64,7 +64,7 @@ public function it_extracts_the_subject() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals('Your order has shipped!', $composer->getEmail()->subject); + $this->assertEquals('Your order has shipped!', $composer->email->subject); } #[Test] @@ -72,7 +72,7 @@ public function it_extracts_the_body() { $composer = Email::compose()->mailable($this->mailable()); - $this->assertEquals("Name: John Doe\n", $composer->getEmail()->body); + $this->assertEquals("Name: John Doe\n", $composer->email->body); } #[Test] diff --git a/tests/QueuedEmailsTest.php b/tests/QueuedEmailsTest.php index 5658f3c..124f57f 100644 --- a/tests/QueuedEmailsTest.php +++ b/tests/QueuedEmailsTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelDatabaseEmails\SendEmailJob; +use Workbench\App\Jobs\CustomSendEmailJob; class QueuedEmailsTest extends TestCase { @@ -105,4 +106,14 @@ public function the_mail_will_be_marked_as_sent_when_job_is_finished() $this->assertTrue($email->isSent()); } + + #[Test] + public function developers_can_choose_their_own_job() + { + Queue::fake(); + + $email = $this->queueEmail(jobClass: CustomSendEmailJob::class); + + Queue::assertPushed(fn (CustomSendEmailJob $job) => $job->email->id === $email->id); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 82ceaa1..08dbe88 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -132,8 +132,8 @@ public function scheduleEmail($scheduledFor, $overwrite = []) return $this->createEmail($overwrite)->later($scheduledFor); } - public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = []) + public function queueEmail($connection = null, $queue = null, $delay = null, $overwrite = [], ?string $jobClass = null) { - return $this->createEmail($overwrite)->queue($connection, $queue, $delay); + return $this->createEmail($overwrite)->queue($connection, $queue, $delay, $jobClass); } } diff --git a/workbench/app/Jobs/CustomSendEmailJob.php b/workbench/app/Jobs/CustomSendEmailJob.php new file mode 100644 index 0000000..ddd3ddd --- /dev/null +++ b/workbench/app/Jobs/CustomSendEmailJob.php @@ -0,0 +1,11 @@ + Date: Sat, 23 Mar 2024 12:48:04 +0100 Subject: [PATCH 46/60] Update UPGRADING.md --- UPGRADING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 2825a32..b38359d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -34,8 +34,8 @@ You can remove the old configuration file. Rename the following environments: -`LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` `→` `DB_EMAILS_TESTING_ENABLED` -`LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` `->` `DB_EMAILS_SEND_IMMEDIATELY` +- `LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` → `DB_EMAILS_TESTING_ENABLED` +- `LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` → `DB_EMAILS_SEND_IMMEDIATELY` The following environments are new: From 95ca3aebd2d844c71620a8ae898b731f1c97f83a Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 23 Mar 2024 13:12:30 +0100 Subject: [PATCH 47/60] Modernize send emails command --- src/SendEmailsCommand.php | 43 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php index b1fcb60..71817ee 100644 --- a/src/SendEmailsCommand.php +++ b/src/SendEmailsCommand.php @@ -5,8 +5,6 @@ namespace Stackkit\LaravelDatabaseEmails; use Illuminate\Console\Command; -use Illuminate\Support\Arr; -use Illuminate\Support\LazyCollection; use Throwable; class SendEmailsCommand extends Command @@ -20,43 +18,28 @@ public function handle(Store $store): void $emails = $store->getQueue(); if ($emails->isEmpty()) { - $this->line('There is nothing to send.'); + $this->components->info('There is nothing to send.'); return; } - $progress = $this->output->createProgressBar($emails->count()); + $this->components->info('Sending '.count($emails).' e-mail(s).'); foreach ($emails as $email) { - $progress->advance(); + $recipients = implode(', ', array_keys($email->recipient)); + $line = str($email->subject)->limit(40).' - '.str($recipients)->limit(40); - rescue( - callback: fn () => $email->send(), - rescue: fn (Throwable $e) => $email->markAsFailed($e) - ); - } + rescue(function () use ($email, $line) { + $email->send(); - $progress->finish(); + $this->components->twoColumnDetail($line, 'DONE'); + }, function (Throwable $e) use ($email, $line) { + $email->markAsFailed($e); - $this->result($emails); - } + $this->components->twoColumnDetail($line, 'FAIL'); + }); + } - /** - * Output a table with the cronjob result. - */ - protected function result(LazyCollection $emails): void - { - $headers = ['ID', 'Recipient', 'Subject', 'Status']; - - $this->line("\n"); - - $this->table($headers, $emails->map(function (Email $email) { - return [ - $email->id, - implode(',', array_column(Arr::wrap($email->recipient), 'recipient')), - $email->subject, - $email->failed ? 'Failed' : 'OK', - ]; - })); + $this->newLine(); } } From 68b3d751a86c5c2de6b70ea59780f16bd28c6468 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 24 Mar 2024 12:18:59 +0100 Subject: [PATCH 48/60] Use mail from name even if it was not specified --- src/EmailComposer.php | 12 +++++++----- tests/MailableReaderTest.php | 2 +- tests/SenderTest.php | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/EmailComposer.php b/src/EmailComposer.php index fc4d67c..1edb0aa 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -152,13 +152,15 @@ public function send(): Email (new MailableReader())->read($this); } - if (! $this->email->from) { - $this->email->from = [ - 'address' => config('mail.from.address'), - 'name' => config('mail.from.name'), - ]; + if (! is_array($this->email->from)) { + $this->email->from = []; } + $this->email->from = [ + 'name' => $this->email->from['name'] ?? config('mail.from.name'), + 'address' => $this->email->from['address'] ?? config('mail.from.address'), + ]; + $this->email->save(); $this->email->refresh(); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 7e15865..6f49725 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -106,7 +106,7 @@ public function it_extracts_the_from_address_and_or_name() $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->from['address']); - $this->assertEquals(null, $email->from['name']); + $this->assertEquals('Example', $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) diff --git a/tests/SenderTest.php b/tests/SenderTest.php index 7083b01..be8a295 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -68,7 +68,7 @@ public function the_email_has_a_correct_from_email_and_from_name() $this->artisan('email:send'); $from = reset($this->sent)->from; $this->assertEquals('marick@dolphiq.nl', key($from)); - $this->assertEquals(null, $from[key($from)]); + $this->assertEquals('From CI test', $from[key($from)]); } #[Test] From c1b30e632604ae4ceae7d609b1e07e507b6b1a51 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 24 Mar 2024 12:26:12 +0100 Subject: [PATCH 49/60] Fix --- tests/MailableReaderTest.php | 2 +- tests/TestCase.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 6f49725..42a5e6b 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -106,7 +106,7 @@ public function it_extracts_the_from_address_and_or_name() $this->assertTrue((bool) $email->from); $this->assertEquals('marick@dolphiq.nl', $email->from['address']); - $this->assertEquals('Example', $email->from['name']); + $this->assertEquals('Laravel', $email->from['name']); $email = Email::compose()->mailable( ($this->mailable()) diff --git a/tests/TestCase.php b/tests/TestCase.php index 08dbe88..abd3a67 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -86,6 +86,8 @@ protected function getEnvironmentSetUp($app) ]); $app['config']->set('mail.driver', 'log'); + + $app['config']->set('mail.from.name', 'Laravel'); } public function createEmail($overwrite = []) From b461908e27baf2c45f28e49e2b351384c1bdc714 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 24 Mar 2024 12:34:11 +0100 Subject: [PATCH 50/60] Update CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cbd09..201d443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 7.0.0 - 2024-03-24 + +**Added** + +- Laravel 10 and 11 support +- Customizable job class for queueing +- Index on emails table to improve performance +- Added support for SQLite and PostgreSQL + +**Changed** + +- Email::compose() has changed. See UPGRADING.md +- Old email table is incompatible - new table will be created + +**Removed** + +- Support for Laravel 6, 7, 8 and 9 +- Email encyption + ## 6.3.0 - 2023-12-30 **Added** From 24743d5601bae46672147959054f8a6a3b4d0648 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Fri, 29 Mar 2024 23:04:52 +0100 Subject: [PATCH 51/60] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 120cc93..59c0a22 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Build Status Latest Stable Version -License +License

# Introduction @@ -133,6 +133,8 @@ However, you will have to use this package's `Attachment` class. ```php +use Stackkit\LaravelDatabaseEmails\Attachment; + Email::compose() ->attachments([ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'), From 3f388b1119e3dda66d4f5b842b646e03b0d8a948 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 6 Apr 2024 18:00:43 +0200 Subject: [PATCH 52/60] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59c0a22..ff91694 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-Build Status +[![Run tests](https://github.com/stackkit/laravel-database-emails/actions/workflows/run-tests.yml/badge.svg)](https://github.com/stackkit/laravel-database-emails/actions/workflows/run-tests.yml) Latest Stable Version License

From 54753d41de3c7aff318d62cbc6ac03186e80cc54 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 6 Apr 2024 18:05:12 +0200 Subject: [PATCH 53/60] Update README.md --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ff91694..d7a559d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -

[![Run tests](https://github.com/stackkit/laravel-database-emails/actions/workflows/run-tests.yml/badge.svg)](https://github.com/stackkit/laravel-database-emails/actions/workflows/run-tests.yml) -Latest Stable Version -License -

+[![Latest Version on Packagist](https://poser.pugx.org/stackkit/laravel-database-emails/v/stable.svg)](https://packagist.org/packages/stackkit/laravel-database-emails) +[![Total Downloads](https://poser.pugx.org/stackkit/laravel-database-emails/downloads.svg)](https://packagist.org/packages/stackkit/laravel-database-emails) # Introduction From 5c258523d876693ff9fe08f9059faba8f806c315 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Tue, 23 Apr 2024 22:23:08 +0200 Subject: [PATCH 54/60] Fix --- tests/TestCase.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index abd3a67..e16e0b5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,7 @@ namespace Tests; -use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Orchestra\Testbench\Concerns\WithWorkbench; @@ -13,7 +13,7 @@ class TestCase extends \Orchestra\Testbench\TestCase { protected $invalid; - use LazilyRefreshDatabase; + use RefreshDatabase; use WithWorkbench; public function setUp(): void @@ -74,15 +74,18 @@ protected function getEnvironmentSetUp($app) 'mysql' => [ 'host' => '127.0.0.1', 'port' => 3307, + 'database' => 'test', + 'username' => 'test', + 'password' => 'test', ], 'pgsql' => [ 'host' => '127.0.0.1', 'port' => 5432, + 'database' => 'test', + 'username' => 'test', + 'password' => 'test', ], }, - 'database' => 'test', - 'username' => 'test', - 'password' => 'test', ]); $app['config']->set('mail.driver', 'log'); From c4161baeba536378bc5461fdbdc2934758833169 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 25 Apr 2024 21:16:41 +0200 Subject: [PATCH 55/60] Fix --- composer.json | 1 + tests/TestCase.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0d16c4d..91cc849 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ "composer update laravel/framework:10.* orchestra/testbench:8.* nunomaduro/collision:7.* --with-all-dependencies" ], "test": [ + "testbench workbench:create-sqlite-db", "testbench package:test" ], "post-autoload-dump": [ diff --git a/tests/TestCase.php b/tests/TestCase.php index e16e0b5..e638a4c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -69,7 +69,7 @@ protected function getEnvironmentSetUp($app) 'driver' => $driver, ...match ($driver) { 'sqlite' => [ - 'database' => ':memory:', + 'database' => database_path('database.sqlite'), ], 'mysql' => [ 'host' => '127.0.0.1', From 98e2498fe2037f21b695749724ba7c5055a2ca6b Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Tue, 26 Nov 2024 18:09:57 +0100 Subject: [PATCH 56/60] Fix tests --- docker-compose.yml | 4 ++++ src/MailableReader.php | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 972092b..f8d1d06 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,8 @@ services: + app: + image: serversideup/php:8.3-fpm + volumes: + - .:/var/www/html mysql: image: mysql:8 ports: diff --git a/src/MailableReader.php b/src/MailableReader.php index bdc3876..fdbfe1e 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -9,6 +9,7 @@ use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Support\Arr; use Illuminate\Support\Traits\Localizable; class MailableReader @@ -158,7 +159,9 @@ private function readBody(EmailComposer $composer): void $mailable = $composer->mailable; $composer->email->view = $mailable->view; - $composer->email->variables = $mailable->buildViewData(); + $composer->email->variables = Arr::except($mailable->buildViewData(), [ + '__laravel_mailable', + ]); $localeToUse = $composer->locale ?? app()->currentLocale(); From a305a016049a8754bb22ebdb9983c3731889f193 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 16 Mar 2025 16:52:47 +0100 Subject: [PATCH 57/60] wip --- .github/workflows/run-tests.yml | 6 +++--- composer.json | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6790273..0fb5351 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -66,9 +66,9 @@ jobs: payload: - { laravel: '11.*', php: '8.3', 'testbench': '9.*', collision: '8.*' } - { laravel: '11.*', php: '8.2', 'testbench': '9.*', collision: '8.*' } - - { laravel: '10.*', php: '8.3', 'testbench': '8.*', collision: '7.*' } - - { laravel: '10.*', php: '8.2', 'testbench': '8.*', collision: '7.*' } - - { laravel: '10.*', php: '8.1', 'testbench': '8.*', collision: '7.*' } + - { laravel: '12.*', php: '8.2', 'testbench': '10.*', collision: '8.*' } + - { laravel: '12.*', php: '8.3', 'testbench': '10.*', collision: '8.*' } + - { laravel: '12.*', php: '8.4', 'testbench': '10.*', collision: '8.*' } name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} diff --git a/composer.json b/composer.json index 91cc849..8f7f638 100644 --- a/composer.json +++ b/composer.json @@ -30,13 +30,13 @@ }, "require": { "ext-json": "*", - "laravel/framework": "^10.0|^11.0", - "doctrine/dbal": "^3.8" + "laravel/framework": "^11.0|^12.0", + "doctrine/dbal": "^4.0" }, "require-dev": { "mockery/mockery": "^1.2", - "orchestra/testbench": "^8.0|^9.0", - "nunomaduro/collision": "^7.0|^8.0", + "orchestra/testbench": "^9.0|^10.0", + "nunomaduro/collision": "^8.0", "laravel/pint": "^1.14" }, "minimum-stability": "dev", From 00356c8897089352465baef9759baf8457315ce4 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 16 Mar 2025 16:53:31 +0100 Subject: [PATCH 58/60] wip --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7a559d..a9fd99f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This package allows you to store and send e-mails using the database. # Requirements -This package requires Laravel 10 or 11. +This package requires Laravel 11 or 12. # Installation From 5c8d5301f4b4e86f62271a74545a1a89034025fc Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 16 Mar 2025 16:55:04 +0100 Subject: [PATCH 59/60] pint --- src/Email.php | 4 ++-- src/EmailComposer.php | 8 ++++---- src/SentMessage.php | 2 +- src/Store.php | 2 +- tests/EnvelopeTest.php | 14 +++++++------- tests/MailableReaderTest.php | 2 +- tests/SenderTest.php | 2 +- tests/TestCase.php | 7 +++---- workbench/app/Models/User.php | 4 +--- 9 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Email.php b/src/Email.php index 5053ca7..d26a568 100644 --- a/src/Email.php +++ b/src/Email.php @@ -57,7 +57,7 @@ class Email extends Model public static function compose(): EmailComposer { - return new EmailComposer(new static()); + return new EmailComposer(new static); } public function isSent(): bool @@ -99,7 +99,7 @@ public function markAsFailed(Throwable $exception): void public function send(): void { - (new Sender())->send($this); + (new Sender)->send($this); } public static function pruneWhen(Closure $closure): void diff --git a/src/EmailComposer.php b/src/EmailComposer.php index 1edb0aa..67efc25 100644 --- a/src/EmailComposer.php +++ b/src/EmailComposer.php @@ -45,7 +45,7 @@ public function __construct(public Email $email) public function envelope(null|Envelope|Closure $envelope = null): self { if ($envelope instanceof Closure) { - $this->envelope = $envelope($this->envelope ?: new Envelope()); + $this->envelope = $envelope($this->envelope ?: new Envelope); return $this; } @@ -58,7 +58,7 @@ public function envelope(null|Envelope|Closure $envelope = null): self public function content(null|Content|Closure $content = null): self { if ($content instanceof Closure) { - $this->content = $content($this->content ?: new Content()); + $this->content = $content($this->content ?: new Content); return $this; } @@ -141,7 +141,7 @@ public function mailable(Mailable $mailable): self { $this->mailable = $mailable; - (new MailableReader())->read($this); + (new MailableReader)->read($this); return $this; } @@ -149,7 +149,7 @@ public function mailable(Mailable $mailable): self public function send(): Email { if ($this->envelope && $this->content) { - (new MailableReader())->read($this); + (new MailableReader)->read($this); } if (! is_array($this->email->from)) { diff --git a/src/SentMessage.php b/src/SentMessage.php index f3dff66..9e075da 100644 --- a/src/SentMessage.php +++ b/src/SentMessage.php @@ -29,7 +29,7 @@ class SentMessage public static function createFromSymfonyMailer(Email $email): SentMessage { - $sentMessage = new self(); + $sentMessage = new self; foreach ($email->getFrom() as $address) { $sentMessage->from[$address->getAddress()] = $address->getName(); diff --git a/src/Store.php b/src/Store.php index bd26544..653c5ff 100644 --- a/src/Store.php +++ b/src/Store.php @@ -17,7 +17,7 @@ class Store */ public function getQueue(): LazyCollection { - $query = new Email(); + $query = new Email; return Email::query() ->whereNull('deleted_at') diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index 4e29bc1..6763939 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -19,13 +19,13 @@ public function test_it_can_set_the_envelope() { $email = Email::compose() ->envelope( - (new Envelope()) + (new Envelope) ->subject('Hey') ->from('asdf@gmail.com') ->to(['johndoe@example.com', 'janedoe@example.com']) ) ->content( - (new Content()) + (new Content) ->view('tests::dummy') ->with(['name' => 'John Doe']) ) @@ -40,7 +40,7 @@ public function test_it_can_set_the_envelope() #[Test] public function test_it_can_pass_user_models() { - $user = (new User())->forceFill([ + $user = (new User)->forceFill([ 'email' => 'johndoe@example.com', 'name' => 'J. Doe', ]); @@ -62,7 +62,7 @@ public function test_it_can_pass_user_models() #[Test] public function users_can_have_a_preferred_email() { - $user = (new UserWithPreferredEmail())->forceFill([ + $user = (new UserWithPreferredEmail)->forceFill([ 'email' => 'johndoe@example.com', 'name' => 'J. Doe', ]); @@ -84,7 +84,7 @@ public function users_can_have_a_preferred_email() #[Test] public function users_can_have_a_preferred_name() { - $user = (new UserWithPreferredName())->forceFill([ + $user = (new UserWithPreferredName)->forceFill([ 'email' => 'johndoe@example.com', 'name' => 'J. Doe', ]); @@ -106,7 +106,7 @@ public function users_can_have_a_preferred_name() #[Test] public function users_can_have_a_preferred_locale() { - $nonLocaleUser = (new User())->forceFill([ + $nonLocaleUser = (new User)->forceFill([ 'email' => 'johndoe@example.com', 'name' => 'J. Doe', ]); @@ -117,7 +117,7 @@ public function users_can_have_a_preferred_locale() ->content(fn (Content $content) => $content->view('locale-email')) ->send(); - $localeUser = (new UserWithPreferredLocale())->forceFill([ + $localeUser = (new UserWithPreferredLocale)->forceFill([ 'email' => 'johndoe@example.com', 'name' => 'J. Doe', ]); diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php index 42a5e6b..bf98627 100644 --- a/tests/MailableReaderTest.php +++ b/tests/MailableReaderTest.php @@ -14,7 +14,7 @@ class MailableReaderTest extends TestCase { private function mailable(): Mailable { - return new TestMailable(); + return new TestMailable; } #[Test] diff --git a/tests/SenderTest.php b/tests/SenderTest.php index be8a295..4f08d2a 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -17,7 +17,7 @@ class SenderTest extends TestCase /** @var array */ public $sent = []; - public function setUp(): void + protected function setUp(): void { parent::setUp(); diff --git a/tests/TestCase.php b/tests/TestCase.php index e638a4c..27e2d16 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -16,7 +16,7 @@ class TestCase extends \Orchestra\Testbench\TestCase use RefreshDatabase; use WithWorkbench; - public function setUp(): void + protected function setUp(): void { parent::setUp(); @@ -26,10 +26,9 @@ public function setUp(): void 1, 1.0, 'test', - new \stdClass(), + new \stdClass, (object) [], - function () { - }, + function () {}, ]; view()->addNamespace('tests', __DIR__.'/views'); diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php index d4ad593..80b9abf 100644 --- a/workbench/app/Models/User.php +++ b/workbench/app/Models/User.php @@ -6,6 +6,4 @@ use Illuminate\Foundation\Auth\User as UserAlias; -class User extends UserAlias -{ -} +class User extends UserAlias {} From 5ca4b7580241bb98945f1a1249ddb8b09602098a Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sun, 16 Mar 2025 17:07:41 +0100 Subject: [PATCH 60/60] wip --- composer.json | 4 ++-- tests/ComposeTest.php | 4 ++-- tests/EnvelopeTest.php | 6 +++--- tests/views/welcome.blade.php | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 tests/views/welcome.blade.php diff --git a/composer.json b/composer.json index 8f7f638..720c56d 100644 --- a/composer.json +++ b/composer.json @@ -45,8 +45,8 @@ "l11": [ "composer update laravel/framework:11.* orchestra/testbench:9.* nunomaduro/collision:8.* --with-all-dependencies" ], - "l10": [ - "composer update laravel/framework:10.* orchestra/testbench:8.* nunomaduro/collision:7.* --with-all-dependencies" + "l12": [ + "composer update laravel/framework:12.* orchestra/testbench:10.* nunomaduro/collision:8.* --with-all-dependencies" ], "test": [ "testbench workbench:create-sqlite-db", diff --git a/tests/ComposeTest.php b/tests/ComposeTest.php index 49e88b9..f823bc9 100644 --- a/tests/ComposeTest.php +++ b/tests/ComposeTest.php @@ -25,7 +25,7 @@ public function models_can_be_attached(): void ->user($user) ->model($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) - ->content(fn (Content $content) => $content->view('welcome')) + ->content(fn (Content $content) => $content->view('tests::welcome')) ->send(); $this->assertEquals($email->model_type, $user->getMorphClass()); @@ -44,7 +44,7 @@ public function models_can_be_empty(): void $email = Email::compose() ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) - ->content(fn (Content $content) => $content->view('welcome')) + ->content(fn (Content $content) => $content->view('tests::welcome')) ->send(); $this->assertNull($email->model_type); diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php index 6763939..bc96437 100644 --- a/tests/EnvelopeTest.php +++ b/tests/EnvelopeTest.php @@ -48,7 +48,7 @@ public function test_it_can_pass_user_models() $email = Email::compose() ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) - ->content(fn (Content $content) => $content->view('welcome')) + ->content(fn (Content $content) => $content->view('tests::welcome')) ->send(); $this->assertEquals( @@ -70,7 +70,7 @@ public function users_can_have_a_preferred_email() $email = Email::compose() ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) - ->content(fn (Content $content) => $content->view('welcome')) + ->content(fn (Content $content) => $content->view('tests::welcome')) ->send(); $this->assertEquals( @@ -92,7 +92,7 @@ public function users_can_have_a_preferred_name() $email = Email::compose() ->user($user) ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey')) - ->content(fn (Content $content) => $content->view('welcome')) + ->content(fn (Content $content) => $content->view('tests::welcome')) ->send(); $this->assertEquals( diff --git a/tests/views/welcome.blade.php b/tests/views/welcome.blade.php new file mode 100644 index 0000000..01f3f00 --- /dev/null +++ b/tests/views/welcome.blade.php @@ -0,0 +1 @@ +Welcome \ No newline at end of file