From 324ba5666ddeb76c7b8bff163666fe304d6c3469 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Thu, 9 May 2024 18:06:39 +0300 Subject: [PATCH] apply email rendering --- src/Mailer.php | 70 +++++++++++++++ src/TemplatedEmail.php | 8 +- src/TemplatedEmailContract.php | 44 ++++++++++ src/TemplatedEmailTrait.php | 116 +++++++++++++++++++++++++ src/View.php | 15 ++-- tests/MailerTest.php | 55 +++++++++++- tests/ViewTest.php | 19 ++++ tests/transport/ArrayTransportTest.php | 10 +-- tests/views/mail/switch.php | 11 +++ 9 files changed, 332 insertions(+), 16 deletions(-) create mode 100644 src/TemplatedEmailContract.php create mode 100644 src/TemplatedEmailTrait.php create mode 100644 tests/views/mail/switch.php diff --git a/src/Mailer.php b/src/Mailer.php index ad6da86..082a2be 100644 --- a/src/Mailer.php +++ b/src/Mailer.php @@ -8,6 +8,7 @@ use Symfony\Component\Mailer\Transport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\RawMessage; +use Yii; use yii1tech\mailer\transport\ArrayTransport; /** @@ -53,6 +54,13 @@ class Mailer extends CApplicationComponent */ private $_transport; + /** + * @var \yii1tech\mailer\View|array view instance or its array configuration. + */ + private $_view = [ + 'class' => View::class, + ]; + public function send(RawMessage $message, ?Envelope $envelope = null): void { foreach ($this->defaultHeaders as $name => $value) { @@ -62,6 +70,10 @@ public function send(RawMessage $message, ?Envelope $envelope = null): void $message->getHeaders()->addHeader($name, $value); } + if ($message instanceof TemplatedEmailContract) { + $message = $this->render($message); + } + $this->getSymfonyMailer()->send($message, $envelope); } @@ -123,6 +135,12 @@ public function getTransport(): TransportInterface return $this->_transport; } + /** + * Creates new transport instance from configuration. + * + * @param callable|string $config transport class name or factory callback. + * @return \Symfony\Component\Mailer\Transport\TransportInterface transport instance. + */ protected function createTransport($config): TransportInterface { if (is_callable($config)) { @@ -135,4 +153,56 @@ protected function createTransport($config): TransportInterface throw new \LogicException('Transport configuration should be either a factory callback or a string class name.'); } + + /** + * @return \yii1tech\mailer\View view instance. + */ + public function getView() + { + if (!is_object($this->_view)) { + $this->_view = Yii::createComponent($this->_view); + } + + return $this->_view; + } + + /** + * @param \yii1tech\mailer\View|array|string $view view instance or its configuration. + * @return static self reference. + */ + public function setView($view): self + { + $this->_view = $view; + + return $this; + } + + /** + * Renders the email message, populating its body parts from the templates. + * + * @param \Symfony\Component\Mime\RawMessage|\Symfony\Component\Mime\Email|\yii1tech\mailer\TemplatedEmailContract $message raw message. + * @return \Symfony\Component\Mime\RawMessage rendered message. + */ + protected function render(RawMessage $message): RawMessage + { + if ($message->isRendered()) { + return $message; + } + + if (($textTemplate = $message->getTextTemplate()) !== null) { + $text = $this->getView()->render($textTemplate, $message->getContext(), $message->getLocale()); + + $message->text($text); + } + + if (($htmlTemplate = $message->getHtmlTemplate()) !== null) { + $html = $this->getView()->render($htmlTemplate, $message->getContext(), $message->getLocale()); + + $message->html($html); + } + + $message->markAsRendered(); + + return $message; + } } \ No newline at end of file diff --git a/src/TemplatedEmail.php b/src/TemplatedEmail.php index 75ad5b9..85bd5ed 100644 --- a/src/TemplatedEmail.php +++ b/src/TemplatedEmail.php @@ -5,10 +5,14 @@ use Symfony\Component\Mime\Email; /** + * TemplatedEmail allows specification of the email body parts as a rendering of the templates. + * + * @see \yii1tech\mailer\View + * * @author Paul Klimov * @since 1.0 */ -class TemplatedEmail extends Email +class TemplatedEmail extends Email implements TemplatedEmailContract { - + use TemplatedEmailTrait; } \ No newline at end of file diff --git a/src/TemplatedEmailContract.php b/src/TemplatedEmailContract.php new file mode 100644 index 0000000..967c480 --- /dev/null +++ b/src/TemplatedEmailContract.php @@ -0,0 +1,44 @@ + + * @since 1.0 + */ +interface TemplatedEmailContract +{ + /** + * @return string|null template name for text body. + */ + public function getTextTemplate(): ?string; + + /** + * @return string|null template name for HTML body. + */ + public function getHtmlTemplate(): ?string; + + /** + * @return array template context variables. + */ + public function getContext(): array; + + /** + * @return string|null locale to be used during template rendering. + */ + public function getLocale(): ?string; + + /** + * @return bool whether the templates have been already rendered. + */ + public function isRendered(): bool; + + /** + * Marks this email as already rendered. + */ + public function markAsRendered(): void; +} \ No newline at end of file diff --git a/src/TemplatedEmailTrait.php b/src/TemplatedEmailTrait.php new file mode 100644 index 0000000..e654507 --- /dev/null +++ b/src/TemplatedEmailTrait.php @@ -0,0 +1,116 @@ + + * @since 1.0 + */ +trait TemplatedEmailTrait +{ + private $htmlTemplate = null; + + private $textTemplate = null; + + private $locale = null; + + private $context = []; + + private $isRendered = false; + + /** + * @param string|null $template template name for text body. + * @return static self reference. + */ + public function textTemplate(?string $template): self + { + $this->textTemplate = $template; + + return $this; + } + + /** + * @param string|null $template template name for HTML body. + * @return static self reference. + */ + public function htmlTemplate(?string $template): self + { + $this->htmlTemplate = $template; + + return $this; + } + + /** + * @param string|null $locale + * @return static self reference. + */ + public function locale(?string $locale): self + { + $this->locale = $locale; + + return $this; + } + + /** + * @param array $context template context variables. + * @return static self reference. + */ + public function context(array $context): self + { + $this->context = $context; + + return $this; + } + + /** + * @return string|null template name for text body. + */ + public function getTextTemplate(): ?string + { + return $this->textTemplate; + } + + /** + * @return string|null template name for HTML body. + */ + public function getHtmlTemplate(): ?string + { + return $this->htmlTemplate; + } + + /** + * @return array template context variables. + */ + public function getContext(): array + { + return $this->context; + } + + /** + * @return string|null locale to be used during template rendering. + */ + public function getLocale(): ?string + { + return $this->locale; + } + + /** + * @return bool whether the templates have been already rendered. + */ + public function isRendered(): bool + { + return $this->isRendered; + } + + /** + * Marks this email as already rendered. + */ + public function markAsRendered(): void + { + $this->isRendered = true; + } +} \ No newline at end of file diff --git a/src/View.php b/src/View.php index 2cb377a..b5a8a67 100644 --- a/src/View.php +++ b/src/View.php @@ -154,14 +154,20 @@ public function renderFile($viewFile, $data = null, $return = false) * * @param string $view name of the view to be rendered. See {@see getViewFile()} for details about how the view script is resolved. * @param array|null $data data to be extracted into PHP variables and made available to the view script. + * @param string|null $locale locale to be used while template rendering. * @return string the rendering result. */ - public function render(string $view, ?array $data = null): string + public function render(string $view, ?array $data = null, ?string $locale = null): string { $originalLayout = $this->layout; + $originalLocale = Yii::app()->getLanguage(); $obInitialLevel = ob_get_level(); try { + if ($locale !== null) { + Yii::app()->setLanguage($locale); + } + $content = $this->renderPartial($view, $data, true); if (!empty($this->layout)) { @@ -175,11 +181,13 @@ public function render(string $view, ?array $data = null): string } $this->layout = $originalLayout; + Yii::app()->setLanguage($originalLocale); throw $e; } $this->layout = $originalLayout; + Yii::app()->setLanguage($originalLocale); return $content; } @@ -204,10 +212,7 @@ public function renderPartial(string $view, ?array $data = null, bool $return = $viewFile = $this->getViewFile($view); if ($viewFile === false) { - throw new \InvalidArgumentException(Yii::t('yii', '{controller} cannot find the requested view "{view}".', [ - '{controller}' => get_class($this), - '{view}' => $view, - ])); + throw new \InvalidArgumentException(get_class($this) . ' cannot find the requested view "' . $view . '".'); } return $this->renderFile($viewFile, $data, $return); diff --git a/tests/MailerTest.php b/tests/MailerTest.php index 0419e1f..b091dc9 100644 --- a/tests/MailerTest.php +++ b/tests/MailerTest.php @@ -8,7 +8,9 @@ use Symfony\Component\Mime\Email; use Yii; use yii1tech\mailer\Mailer; +use yii1tech\mailer\TemplatedEmail; use yii1tech\mailer\transport\ArrayTransport; +use yii1tech\mailer\View; class MailerTest extends TestCase { @@ -89,10 +91,10 @@ public function testDefaultHeaders(): void ], ]); - $email = new Email(); - $email->addTo('test@example.com'); - $email->subject('Test subject'); - $email->text('Test body'); + $email = (new Email()) + ->addTo('test@example.com') + ->subject('Test subject') + ->text('Test body'); $mailer->send($email); @@ -102,4 +104,49 @@ public function testDefaultHeaders(): void $this->assertSame('My App', $sentMessage->getFrom()[0]->getName()); $this->assertSame('test-bcc@example.com', $sentMessage->getBcc()[0]->getAddress()); } + + public function testSetupView(): void + { + /** @var Mailer $mailer */ + $mailer = Yii::createComponent([ + 'class' => Mailer::class, + 'view' => [ + 'class' => View::class, + 'layout' => 'test-layout', + ], + ]); + + $view = $mailer->getView(); + + $this->assertTrue($view instanceof View); + $this->assertSame('test-layout', $view->layout); + } + + public function testRender(): void + { + $transport = new ArrayTransport(); + + /** @var Mailer $mailer */ + $mailer = Yii::createComponent([ + 'class' => Mailer::class, + 'transport' => $transport, + ]); + + $email = (new TemplatedEmail()) + ->addFrom('noreply@example.com') + ->addTo('test@example.com') + ->subject('Test subject') + ->textTemplate('plain') + ->htmlTemplate('plain') + ->context([ + 'name' => 'John Doe', + ]); + + $mailer->send($email); + + $sentMessage = $transport->getLastSentMessage(); + + $this->assertStringContainsString('Name = John Doe', $sentMessage->getTextBody()); + $this->assertStringContainsString('Name = John Doe', $sentMessage->getHtmlBody()); + } } \ No newline at end of file diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 77503d2..389e54e 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -2,6 +2,7 @@ namespace yii1tech\mailer\test; +use Yii; use yii1tech\mailer\View; class ViewTest extends TestCase @@ -54,4 +55,22 @@ public function testSetupViewRenderer(): void $this->assertTrue($viewRenderer instanceof \CPradoViewRenderer); } + + public function testRestoreOrigins(): void + { + $view = new View(); + $view->layout = 'default-layout'; + + $content = $view->render('switch', [ + 'name' => 'John Doe', + ], 'ru'); + + $this->assertStringContainsString('Name = John Doe', $content); + $this->assertStringContainsString('Locale = ru', $content); + $this->assertStringContainsString('', $content); + $this->assertStringContainsString('', $content); + + $this->assertSame('default-layout', $view->layout); + $this->assertSame('en_us', Yii::app()->getLanguage()); + } } \ No newline at end of file diff --git a/tests/transport/ArrayTransportTest.php b/tests/transport/ArrayTransportTest.php index 1103565..1d7e536 100644 --- a/tests/transport/ArrayTransportTest.php +++ b/tests/transport/ArrayTransportTest.php @@ -20,11 +20,11 @@ public function testSend(): void 'transport' => $transport, ]); - $email = new Email(); - $email->from('noreply@example.com'); - $email->addTo('test@example.com'); - $email->subject('Test subject'); - $email->text('Test body'); + $email = (new Email()) + ->from('noreply@example.com') + ->addTo('test@example.com') + ->subject('Test subject') + ->text('Test body'); $mailer->send($email); diff --git a/tests/views/mail/switch.php b/tests/views/mail/switch.php new file mode 100644 index 0000000..ff089cb --- /dev/null +++ b/tests/views/mail/switch.php @@ -0,0 +1,11 @@ +layout = 'layout'; +?> +Test switch mail template +Name = +Locale = getLanguage(); ?> +