diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf index ec6982d..a56ed29 100644 --- a/infrastructure/nginx/nginx.conf +++ b/infrastructure/nginx/nginx.conf @@ -2,6 +2,9 @@ server { root /app/web; listen 80 default_server; + client_max_body_size 10M; + client_body_buffer_size 10M; + location / { # try to serve file directly, fallback to app.php try_files $uri /app.php$is_args$args; diff --git a/src/Outstack/Enveloper/Resolution/AttachmentResolver.php b/src/Outstack/Enveloper/Resolution/AttachmentResolver.php index 1929fdd..5afe6f5 100644 --- a/src/Outstack/Enveloper/Resolution/AttachmentResolver.php +++ b/src/Outstack/Enveloper/Resolution/AttachmentResolver.php @@ -20,10 +20,9 @@ public function __construct(TemplateLanguage $language) public function resolve(AttachmentTemplate $template, object $parameters) { return new Attachment( - $this->language->render( - $template->getContents(), - $parameters - ), + $template->isStatic() + ? $template->getContents() + : $this->language->render($template->getContents(), $parameters), $this->language->render( $template->getFilename(), $parameters diff --git a/src/Outstack/Enveloper/Resolution/Twig/TwigEnveloperExtension.php b/src/Outstack/Enveloper/Resolution/Twig/TwigEnveloperExtension.php new file mode 100644 index 0000000..450882b --- /dev/null +++ b/src/Outstack/Enveloper/Resolution/Twig/TwigEnveloperExtension.php @@ -0,0 +1,14 @@ + new \Twig_SimpleFilter('base64_decode', 'base64_decode') + ]; + } + +} \ No newline at end of file diff --git a/src/Outstack/Enveloper/Resolution/Twig/TwigTemplateLanguage.php b/src/Outstack/Enveloper/Resolution/Twig/TwigTemplateLanguage.php index 248101a..e8b2f87 100644 --- a/src/Outstack/Enveloper/Resolution/Twig/TwigTemplateLanguage.php +++ b/src/Outstack/Enveloper/Resolution/Twig/TwigTemplateLanguage.php @@ -18,6 +18,7 @@ public function __construct(Twig_Environment $twig = null) $twig = new Twig_Environment(new \Twig_Loader_Chain()); } + $twig->addExtension(new TwigEnveloperExtension()); $this->twig = $twig; } diff --git a/src/Outstack/Enveloper/Templates/AttachmentTemplate.php b/src/Outstack/Enveloper/Templates/AttachmentTemplate.php index 7896b4e..7e48e58 100644 --- a/src/Outstack/Enveloper/Templates/AttachmentTemplate.php +++ b/src/Outstack/Enveloper/Templates/AttachmentTemplate.php @@ -16,12 +16,22 @@ class AttachmentTemplate * @var null|string */ private $iterateOver; + /** + * @var bool + */ + private $static; - public function __construct(string $contents, string $filename, ?string $iterateOver = null) + public function __construct(bool $static, string $contents, string $filename, ?string $iterateOver = null) { $this->contents = $contents; $this->filename = $filename; $this->iterateOver = $iterateOver; + $this->static = $static; + } + + public function isStatic(): bool + { + return $this->static; } public function getContents(): string diff --git a/src/Outstack/Enveloper/Templates/Loader/ConfigurationParser/TemplateConfiguration.php b/src/Outstack/Enveloper/Templates/Loader/ConfigurationParser/TemplateConfiguration.php index 63b5b1a..79cb3fd 100644 --- a/src/Outstack/Enveloper/Templates/Loader/ConfigurationParser/TemplateConfiguration.php +++ b/src/Outstack/Enveloper/Templates/Loader/ConfigurationParser/TemplateConfiguration.php @@ -86,7 +86,8 @@ public function getConfigTreeBuilder() ->arrayNode('attachments') ->prototype('array') ->children() - ->scalarNode('contents')->isRequired()->end() + ->scalarNode('source')->end() + ->scalarNode('contents')->end() ->scalarNode('filename')->isRequired()->end() ->scalarNode('iterateOver')->defaultNull()->end() ->end() diff --git a/src/Outstack/Enveloper/Templates/Loader/FilesystemLoader.php b/src/Outstack/Enveloper/Templates/Loader/FilesystemLoader.php index da285b6..38f99b2 100644 --- a/src/Outstack/Enveloper/Templates/Loader/FilesystemLoader.php +++ b/src/Outstack/Enveloper/Templates/Loader/FilesystemLoader.php @@ -68,20 +68,29 @@ public function find(string $name): Template $textTemplate, $config['content']['html'], $htmlTemplate, - $this->parseAttachmentListTemplate($config['attachments']) + $this->parseAttachmentListTemplate($config['attachments'], $name) ); } - private function parseAttachmentListTemplate(array $attachments) + private function parseAttachmentListTemplate(array $attachments, string $templateName) { return new AttachmentListTemplate( - array_map([$this, 'parseAttachmentTemplate'], $attachments) + array_map( + function($attachment) use ($templateName) { + return $this->parseAttachmentTemplate($attachment, $templateName); + }, + $attachments) ); } - private function parseAttachmentTemplate(array $template) + private function parseAttachmentTemplate(array $template, string $templateName) { - return new AttachmentTemplate($template['contents'], $template['filename'], $template['iterateOver'] ?? null); + $static = false; + if (!array_key_exists('content', $template) && array_key_exists('source', $template)) { + $static = true; + $template['contents'] = $this->filesystem->read("$templateName/{$template['source']}"); + } + return new AttachmentTemplate($static, $template['contents'], $template['filename'], $template['iterateOver'] ?? null); } private function parseRecipientListTemplate(array $recipients): ParticipantListTemplate diff --git a/tests/Functional/AttachmentHandlingFunctionalTest.php b/tests/Functional/AttachmentHandlingFunctionalTest.php new file mode 100644 index 0000000..406f575 --- /dev/null +++ b/tests/Functional/AttachmentHandlingFunctionalTest.php @@ -0,0 +1,151 @@ +mailerSpy = self::$kernel->getContainer()->get(SwiftMailerInterface::class); + } + + public function test_attachments_sent() + { + $request = new Request( + '/outbox', + 'POST', + $this->convertToStream(json_encode([ + 'template' => 'message-with-attachments', + 'parameters' => [ + 'email' => 'bob@example.com', + 'attachments' => [ + ['contents' => base64_encode('This is a note'), 'filename' => 'note.txt'] + ] + ] + ])) + ); + + $response = $this->client->sendRequest($request); + $this->assertEquals(204, $response->getStatusCode()); + + $this->assertCountSentMessages(1); + $this->assertMessageSent( + function(\Swift_Message $message) { + $expectedContents = base64_encode('This is a note'); + $expected = + 'Content-Type: application/octet-stream; name=note.txt' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=note.txt' . "\r\n" . "\r\n" . + $expectedContents . "\r\n" . "\r\n" . + '--' + ; + + foreach (explode($message->getBoundary(), (string) $message) as $part) { + if (trim($part) == trim($expected)) { + return true; + } + } + + throw new \LogicException("No matching attachment found"); + } + ); + } + + public function test_large_attachment_sent() + { + $largeAttachment = random_bytes(1048576 * 7); + $request = new Request( + '/outbox', + 'POST', + $this->convertToStream(json_encode([ + 'template' => 'message-with-attachments', + 'parameters' => [ + 'email' => 'bob@example.com', + 'attachments' => [ + ['contents' => base64_encode($largeAttachment), 'filename' => 'random.txt'] + ] + ] + ])) + ); + + $response = $this->client->sendRequest($request); + + $this->assertEquals(204, $response->getStatusCode()); + + $this->assertCountSentMessages(1); + $this->assertMessageSent( + function(\Swift_Message $message) use ($largeAttachment) { + $expectedContents = implode("\r\n", str_split(base64_encode($largeAttachment), 76)); + $expected = + 'Content-Type: application/octet-stream; name=random.txt' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=random.txt' . "\r\n" . "\r\n" . + $expectedContents . "\r\n" . "\r\n" . + '--' + ; + + foreach (explode($message->getBoundary(), (string) $message) as $part) { + if (trim($part) == trim($expected)) { + return true; + } + } + + throw new \LogicException("No matching attachment found"); + } + ); + } + + public function test_static_attachment_sent() + { + $expectedAttachment = 'static attachment content'; + $request = new Request( + '/outbox', + 'POST', + $this->convertToStream(json_encode([ + 'template' => 'message-with-static-attachments', + 'parameters' => [ + 'email' => 'bob@example.com' + ] + ])) + ); + + $response = $this->client->sendRequest($request); + + $this->assertEquals(204, $response->getStatusCode()); + + $this->assertCountSentMessages(1); + $this->assertMessageSent( + function(\Swift_Message $message) use ($expectedAttachment) { + $expectedContents = implode("\r\n", str_split(base64_encode($expectedAttachment), 76)); + $expected = + 'Content-Type: application/octet-stream; name=attachment.txt' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=attachment.txt' . "\r\n" . "\r\n" . + $expectedContents . "\r\n" . "\r\n" . + '--' + ; + + foreach (explode($message->getBoundary(), (string) $message) as $part) { + if (trim($part) == trim($expected)) { + return true; + } + } + + throw new \LogicException("No matching attachment found"); + } + ); + } +} \ No newline at end of file diff --git a/tests/Functional/EmailSendingFunctionalTest.php b/tests/Functional/EmailSendingFunctionalTest.php index d0f26c2..c0d8ff2 100644 --- a/tests/Functional/EmailSendingFunctionalTest.php +++ b/tests/Functional/EmailSendingFunctionalTest.php @@ -138,45 +138,4 @@ function(\Swift_Message $message) { ); } - public function test_attachments_sent() - { - $request = new Request( - '/outbox', - 'POST', - $this->convertToStream(json_encode([ - 'template' => 'message-with-attachments', - 'parameters' => [ - 'email' => 'bob@example.com', - 'attachments' => [ - ['contents' => 'This is a note', 'filename' => 'note.txt'] - ] - ] - ])) - ); - - $response = $this->client->sendRequest($request); - $this->assertEquals(204, $response->getStatusCode()); - - $this->assertCountSentMessages(1); - $this->assertMessageSent( - function(\Swift_Message $message) { - $expectedContents = base64_encode('This is a note'); - $expected = - 'Content-Type: application/octet-stream; name=note.txt' . "\r\n" . - 'Content-Transfer-Encoding: base64' . "\r\n" . - 'Content-Disposition: attachment; filename=note.txt' . "\r\n" . "\r\n" . - $expectedContents . "\r\n" . "\r\n" . - '--' - ; - - foreach (explode($message->getBoundary(), (string) $message) as $part) { - if (trim($part) == trim($expected)) { - return true; - } - } - - throw new \LogicException("No matching attachment found"); - } - ); - } } \ No newline at end of file diff --git a/tests/Unit/Outstack/Enveloper/Resolution/AttachmentListResolverTest.php b/tests/Unit/Outstack/Enveloper/Resolution/AttachmentListResolverTest.php index 16ccf24..ca95369 100644 --- a/tests/Unit/Outstack/Enveloper/Resolution/AttachmentListResolverTest.php +++ b/tests/Unit/Outstack/Enveloper/Resolution/AttachmentListResolverTest.php @@ -36,7 +36,7 @@ public function test_resolves_template_with_iterated_value() $this->sut->resolveAttachmentList( new AttachmentListTemplate( [ - new AttachmentTemplate('{{ item.data }}', '{{ item.filename }}', 'attachments') + new AttachmentTemplate(false, '{{ item.data }}', '{{ item.filename }}', 'attachments') ] ), (object) [ @@ -61,8 +61,8 @@ public function test_resolves_multiple_attachments() $this->sut->resolveAttachmentList( new AttachmentListTemplate( [ - new AttachmentTemplate('{{ attachments[0].data }}', '{{ attachments[0].filename }}'), - new AttachmentTemplate('{{ attachments[1].data }}', '{{ attachments[1].filename }}') + new AttachmentTemplate(false, '{{ attachments[0].data }}', '{{ attachments[0].filename }}'), + new AttachmentTemplate(false, '{{ attachments[1].data }}', '{{ attachments[1].filename }}') ] ), (object) [ diff --git a/tests/Unit/Outstack/Enveloper/Resolution/AttachmentResolverTest.php b/tests/Unit/Outstack/Enveloper/Resolution/AttachmentResolverTest.php index c59033e..4a29ebb 100644 --- a/tests/Unit/Outstack/Enveloper/Resolution/AttachmentResolverTest.php +++ b/tests/Unit/Outstack/Enveloper/Resolution/AttachmentResolverTest.php @@ -28,6 +28,7 @@ public function test_simple_txt_resolved() ), $this->sut->resolve( new AttachmentTemplate( + false, '{{ string1 }} - {{ string2 }}', '{{ string3 }}.txt', null diff --git a/tests/Unit/Outstack/Enveloper/Resolution/MessageResolverTest.php b/tests/Unit/Outstack/Enveloper/Resolution/MessageResolverTest.php index 427cccb..4482222 100644 --- a/tests/Unit/Outstack/Enveloper/Resolution/MessageResolverTest.php +++ b/tests/Unit/Outstack/Enveloper/Resolution/MessageResolverTest.php @@ -97,7 +97,7 @@ public function test_it_resolves_message_with_attachments() 'template.html.twig', '
Welcome to app {{ user.name }}', new AttachmentListTemplate([ - new AttachmentTemplate('attachment {{ number }}', 'a{{ number }}.txt') + new AttachmentTemplate(false, 'attachment {{ number }}', 'a{{ number }}.txt') ]) ), (object) [ diff --git a/tests/Unit/Outstack/Enveloper/Templates/Loader/FilesystemLoaderTest.php b/tests/Unit/Outstack/Enveloper/Templates/Loader/FilesystemLoaderTest.php index e67c6f1..0100ad0 100644 --- a/tests/Unit/Outstack/Enveloper/Templates/Loader/FilesystemLoaderTest.php +++ b/tests/Unit/Outstack/Enveloper/Templates/Loader/FilesystemLoaderTest.php @@ -122,7 +122,7 @@ public function test_finds_template_with_attachment() 'new-user-welcome.html.twig', $html, new AttachmentListTemplate([ - new AttachmentTemplate('{{ contents }}', '{{ filename }}') + new AttachmentTemplate(false, '{{ contents }}', '{{ filename }}') ]) ), $this->sut->find('new-user-welcome') @@ -165,7 +165,7 @@ public function test_finds_template_with_attachment_iterator() 'new-user-welcome.html.twig', $html, new AttachmentListTemplate([ - new AttachmentTemplate('{{ item.contents }}', '{{ item.filename }}', 'attachments') + new AttachmentTemplate(false, '{{ item.contents }}', '{{ item.filename }}', 'attachments') ]) ), $this->sut->find('new-user-welcome') diff --git a/tests/data/templates/message-with-attachments/message-with-attachments.html.twig b/tests/data/templates/message-with-attachments/message-with-attachments.html.twig deleted file mode 100644 index 1fe292d..0000000 --- a/tests/data/templates/message-with-attachments/message-with-attachments.html.twig +++ /dev/null @@ -1,5 +0,0 @@ - -
-Message with attachments
- - \ No newline at end of file diff --git a/tests/data/templates/message-with-attachments/message-with-attachments.meta.yml b/tests/data/templates/message-with-attachments/message-with-attachments.meta.yml index 0be83b8..7ca8064 100644 --- a/tests/data/templates/message-with-attachments/message-with-attachments.meta.yml +++ b/tests/data/templates/message-with-attachments/message-with-attachments.meta.yml @@ -5,7 +5,7 @@ recipients: to: - "{{ email }}" content: - html: "message-with-attachments.html.twig" + html: "message-with-attachments.mjml.twig" text: "message-with-attachments.text.twig" attachments: - - { contents: "{{ item.contents }}", filename: "{{ item.filename }}", iterateOver: "attachments" } \ No newline at end of file + - { contents: "{% autoescape false %}{{ item.contents|base64_decode }}{% endautoescape %}", filename: "{{ item.filename }}", iterateOver: "attachments" } \ No newline at end of file diff --git a/tests/data/templates/message-with-attachments/message-with-attachments.mjml.twig b/tests/data/templates/message-with-attachments/message-with-attachments.mjml.twig new file mode 100644 index 0000000..9a08204 --- /dev/null +++ b/tests/data/templates/message-with-attachments/message-with-attachments.mjml.twig @@ -0,0 +1,7 @@ +