From 2c720048b975c3454ad8029ac6ea46f0b9ce2eca Mon Sep 17 00:00:00 2001 From: Tim Gieseking Date: Mon, 12 Dec 2022 06:00:50 -0600 Subject: [PATCH 1/2] Adds the ability to attach files (#9) --- src/SendGridMessage.php | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/SendGridMessage.php b/src/SendGridMessage.php index 09aef40..b659511 100644 --- a/src/SendGridMessage.php +++ b/src/SendGridMessage.php @@ -6,6 +6,7 @@ use SendGrid\Mail\From; use SendGrid\Mail\Mail; use SendGrid\Mail\ReplyTo; +use SendGrid\Mail\Attachment; class SendGridMessage { @@ -42,6 +43,13 @@ class SendGridMessage */ public $payload = []; + /** + * An array of attachments for the message. + * + * @var array + */ + public $attachments = []; + /** * The sandbox mode for SendGrid * @@ -103,6 +111,26 @@ public function payload($payload) return $this; } + public function attach($attachments) + { + /* + $attachments should be an array of individual attachments. content should be base64 encoded. + + Example: + $attachments = array( + array( + 'content' => base64_encode($content), + 'type' => 'application/pdf', + 'filename' => 'filename.pdf' + ) + ); + */ + + $this->attachments = $attachments; + + return $this; + } + /** * @return Mail */ @@ -124,6 +152,24 @@ public function build(): Mail $email->addDynamicTemplateData((string) $key, $value); } + if (is_array($this->attachments) && !empty($this->attachments)) { + foreach ($this->attachments as $attachment) { + $disposition = (isset($attachment['disposition'])) ? strtolower($attachment['disposition']) : "attachment"; + + $sgAttachment = new Attachment(); + $sgAttachment->setType($attachment['type']); + $sgAttachment->setContent($attachment['content']); + $sgAttachment->setDisposition($disposition); + $sgAttachment->setFilename($attachment['filename']); + + if ($disposition === "inline") { + $sgAttachment->setContentID($attachment['filename']); + } + + $email->addAttachment($sgAttachment); + } + } + return $email; } From e35362c6525f1e81571558592ab79bf1a9e3df20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=96z=C4=B1=C5=9F=C4=B1k?= Date: Mon, 12 Dec 2022 15:23:10 +0200 Subject: [PATCH 2/2] Support Attachments and Inline Attachments (#10) * refactor attachment support * test attachments * support testbench 5,6,7 * update docs --- CHANGELOG.md | 4 + README.md | 15 ++++ composer.json | 8 +- phpunit.xml | 3 + src/SendGridMessage.php | 137 +++++++++++++++++++++++++++------- tests/SendGridChannelTest.php | 1 - tests/SendGridMessageTest.php | 116 +++++++++++++++++++++++++++- tests/TestCase.php | 7 ++ tests/fixtures/blank.jpg | Bin 0 -> 631 bytes 9 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 tests/TestCase.php create mode 100644 tests/fixtures/blank.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 551c687..39137eb 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes will be documented in this file +## 2.3.0 - 2022-12-12 + +- Attachments and inline attachments are now supported. + ## 2.2.0 - 2022-09-24 - You can now pass nested arrays, numbers, bools as the payload. Previously, the library only accepted strings. (Thanks [timstl](https://github.com/swiftmade/laravel-sendgrid-notification-channel/pull/7)) diff --git a/README.md b/README.md index 5935275..100c6ed 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,21 @@ return (new SendGridMessage('Your SendGrid template ID')) When making a request with sandbox mode enabled, Sendgrid will validate the form, type, and shape of your request. No email will be sent. You can read more about the sandbox mode [here](https://docs.sendgrid.com/for-developers/sending-email/sandbox-mode). +### Attachments + +You can attach or embed (inline attachment) files to your messages. `SendGridMessage` object exposes the following methods to help you do that: + +- `attach($file, $options)` +- `attachData($data, $name, $options)` +- `embed($file, $options)` +- `embedData($data, $name, $options)` + +**Good to know:** + +- While using `attachData` and `embedData` you must always pass the `mime` key in the options array. +- You can use the `as` key in the options to change the filename to appears in the email. (e.g. `attach($file, ['as' => 'invoice-3252.pdf'])`) +- `embed` and `embedData` methods will return the ContentID with `cid:` in front (e.g. `embed('avatar.jpg') -> "cid:avatar.jpg"`). + ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. diff --git a/composer.json b/composer.json index b41a3e5..784af9e 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.8", - "mockery/mockery": "^1.3", + "mockery/mockery": "^1.5", + "orchestra/testbench": "^5.0|^6.0|^7.0", "phpunit/phpunit": "^8.4|^9.0" }, "autoload": { @@ -34,7 +35,10 @@ }, "scripts": { "test": "phpunit", - "test:coverage": "phpunit --coverage-text --coverage-clover=coverage.clover" + "test:coverage": "phpunit --coverage-text --coverage-clover=coverage.clover", + "post-autoload-dump": [ + "@php vendor/bin/testbench package:discover --ansi" + ] }, "config": { "sort-packages": true diff --git a/phpunit.xml b/phpunit.xml index 3f8a47c..d8a38f1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,4 +19,7 @@ src/ + + + diff --git a/src/SendGridMessage.php b/src/SendGridMessage.php index b659511..41822d4 100644 --- a/src/SendGridMessage.php +++ b/src/SendGridMessage.php @@ -2,11 +2,13 @@ namespace NotificationChannels\SendGrid; +use RuntimeException; use SendGrid\Mail\To; use SendGrid\Mail\From; use SendGrid\Mail\Mail; use SendGrid\Mail\ReplyTo; use SendGrid\Mail\Attachment; +use Illuminate\Support\Facades\File; class SendGridMessage { @@ -111,26 +113,116 @@ public function payload($payload) return $this; } - public function attach($attachments) + /** + * Attach a file to the message. + * + * array( + * 'as' => 'name.pdf', + * 'mime' => 'application/pdf', + * ) + * + * @param string $file + * @param array $options + * @return $this + */ + public function attach($file, array $options = []) { - /* - $attachments should be an array of individual attachments. content should be base64 encoded. - - Example: - $attachments = array( - array( - 'content' => base64_encode($content), - 'type' => 'application/pdf', - 'filename' => 'filename.pdf' - ) + if (! isset($options['mime'])) { + $options['mime'] = File::mimeType($file); + } + + // TODO: Support "Attachable" and "Attachment" types. + + return $this->attachData( + file_get_contents($file), + $file, + $options ); - */ + } - $this->attachments = $attachments; + /** + * Attach in-memory data as an attachment. + * + * @param string $data + * @param string $name + * @param array $options + * @return $this + */ + public function attachData($data, $name, array $options) + { + if (! isset($options['mime'])) { + throw new RuntimeException( + 'Cannot predict mimetype of "' . $name . '". ' + . 'Provide a valid \'mime\' in $options parameter.' + ); + } + + $showFilenameAs = isset($options['as']) + ? $options['as'] + : basename($name); + + $attachment = new Attachment( + base64_encode($data), + $options['mime'], + $showFilenameAs, + isset($options['inline']) ? 'inline' : 'attachment' + ); + + if (isset($options['inline'])) { + $attachment->setContentID($showFilenameAs); + } + + $this->attachments[] = $attachment; return $this; } + /** + * Add inline attachment from a file in the message and get the CID. + * + * array( + * 'as' => 'name.pdf', + * 'mime' => 'application/pdf', + * ) + * + * @param string $file + * @return string + */ + public function embed($file, array $options = []) + { + if (! isset($options['mime'])) { + $options['mime'] = File::mimeType($file); + } + + // TODO: Support "Attachable" and "Attachment" types. + + return $this->embedData( + file_get_contents($file), + $file, + $options + ); + } + + /** + * Add inline attachments from in-memory data in the message and get the CID. + * + * @param string $data + * @param string $name + * @param string|null $contentType + * @return string + */ + public function embedData($data, $name, array $options) + { + $this->attachData($data, $name, array_merge( + $options, + ['inline' => true] + )); + + $lastIndex = count($this->attachments) - 1; + + return "cid:" . $this->attachments[$lastIndex]->getContentID(); + } + /** * @return Mail */ @@ -152,24 +244,11 @@ public function build(): Mail $email->addDynamicTemplateData((string) $key, $value); } - if (is_array($this->attachments) && !empty($this->attachments)) { - foreach ($this->attachments as $attachment) { - $disposition = (isset($attachment['disposition'])) ? strtolower($attachment['disposition']) : "attachment"; - - $sgAttachment = new Attachment(); - $sgAttachment->setType($attachment['type']); - $sgAttachment->setContent($attachment['content']); - $sgAttachment->setDisposition($disposition); - $sgAttachment->setFilename($attachment['filename']); - - if ($disposition === "inline") { - $sgAttachment->setContentID($attachment['filename']); - } - - $email->addAttachment($sgAttachment); - } + foreach ($this->attachments as $attachment) { + $email->addAttachment($attachment); } + return $email; } diff --git a/tests/SendGridChannelTest.php b/tests/SendGridChannelTest.php index 98b0889..ec1dd24 100644 --- a/tests/SendGridChannelTest.php +++ b/tests/SendGridChannelTest.php @@ -5,7 +5,6 @@ use Mockery; use SendGrid; use SendGrid\Response; -use PHPUnit\Framework\TestCase; use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; use NotificationChannels\SendGrid\SendGridChannel; diff --git a/tests/SendGridMessageTest.php b/tests/SendGridMessageTest.php index f615913..99b3830 100644 --- a/tests/SendGridMessageTest.php +++ b/tests/SendGridMessageTest.php @@ -2,7 +2,7 @@ namespace NotificationChannels\SendGrid\Test; -use PHPUnit\Framework\TestCase; +use SendGrid\Mail\Attachment; use NotificationChannels\SendGrid\SendGridMessage; class SendGridMessageTest extends TestCase @@ -50,4 +50,118 @@ public function testSandboxMode() 'Sandbox mode is disabled in Sendgrid mail settings' ); } + + public function testAttachmentFromPath() + { + $path = __DIR__ . '/fixtures/blank.jpg'; + + $message = new SendGridMessage('template-id'); + $message->attach(__DIR__ . '/fixtures/blank.jpg'); + + /** + * @var Attachment + */ + $attachment = $message->attachments[0]; + + // Contents are base64-encoded + $this->assertEquals( + base64_encode(file_get_contents($path)), + $attachment->getContent() + ); + + $this->assertEquals('blank.jpg', $attachment->getFilename()); + $this->assertEquals('image/jpeg', $attachment->getType()); + $this->assertEquals('attachment', $attachment->getDisposition()); + + // Let's test the options array. + $message->attach(__DIR__ . '/fixtures/blank.jpg', [ + 'as' => 'custom.png', + 'mime' => 'image/png', + ]); + + /** + * @var Attachment + */ + $attachment2 = $message->attachments[1]; + $this->assertEquals('custom.png', $attachment2->getFilename()); + $this->assertEquals('image/png', $attachment2->getType()); + $this->assertEquals('attachment', $attachment2->getDisposition()); + } + + public function testAttachmentFromData() + { + $path = __DIR__ . '/fixtures/blank.jpg'; + $contents = file_get_contents($path); + + $message = new SendGridMessage('template-id'); + $message->attachData($contents, 'blank.jpg', ['mime' => 'image/jpeg']); + + /** + * @var Attachment + */ + $attachment = $message->attachments[0]; + + // Contents are base64-encoded + $this->assertEquals( + base64_encode($contents), + $attachment->getContent() + ); + + $this->assertEquals('blank.jpg', $attachment->getFilename()); + $this->assertEquals('image/jpeg', $attachment->getType()); + $this->assertEquals('attachment', $attachment->getDisposition()); + } + + public function testEmbeddingFromPath() + { + $path = __DIR__ . '/fixtures/blank.jpg'; + + $message = new SendGridMessage('template-id'); + $contentId = $message->embed(__DIR__ . '/fixtures/blank.jpg'); + + $this->assertEquals('cid:blank.jpg', $contentId); + + /** + * @var Attachment + */ + $attachment = $message->attachments[0]; + + // Contents are base64-encoded + $this->assertEquals( + base64_encode(file_get_contents($path)), + $attachment->getContent() + ); + + $this->assertEquals('blank.jpg', $attachment->getFilename()); + $this->assertEquals('image/jpeg', $attachment->getType()); + $this->assertEquals('inline', $attachment->getDisposition()); + $this->assertEquals('blank.jpg', $attachment->getContentID()); + } + + public function testEmbeddingFromData() + { + $path = __DIR__ . '/fixtures/blank.jpg'; + $contents = file_get_contents($path); + + $message = new SendGridMessage('template-id'); + $contentId = $message->embedData($contents, 'blank.png', ['mime' => 'image/png']); + + $this->assertEquals('cid:blank.png', $contentId); + + /** + * @var Attachment + */ + $attachment = $message->attachments[0]; + + // Contents are base64-encoded + $this->assertEquals( + base64_encode(file_get_contents($path)), + $attachment->getContent() + ); + + $this->assertEquals('blank.png', $attachment->getFilename()); + $this->assertEquals('image/png', $attachment->getType()); + $this->assertEquals('inline', $attachment->getDisposition()); + $this->assertEquals('blank.png', $attachment->getContentID()); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..b9f9f27 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,7 @@ +^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<ECr+Na zbot8FYu9hwy!G(W<0ns_J%91?)yGetzkL1n{m0K=Ab&A3Fhjfr_ZgbM1cClyVqsxs zVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamA3L7B$%azX<@d&d)*s literal 0 HcmV?d00001