diff --git a/app/Providers/MailCssInlinerServiceProvider.php b/app/Providers/MailCssInlinerServiceProvider.php new file mode 100644 index 000000000000..040bcd76ba1b --- /dev/null +++ b/app/Providers/MailCssInlinerServiceProvider.php @@ -0,0 +1,41 @@ +publishes([ + __DIR__ . '/../config/css-inliner.php' => base_path('config/css-inliner.php'), + ], 'config'); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app->singleton(CssInlinerPlugin::class, function ($app) { + return new CssInlinerPlugin([]); + }); + + $this->app->afterResolving('mail.manager', function (MailManager $mailManager) { + $mailManager->getSwiftMailer()->registerPlugin($this->app->make(CssInlinerPlugin::class)); + return $mailManager; + }); + } +} diff --git a/app/Utils/CssInlinerPlugin.php b/app/Utils/CssInlinerPlugin.php new file mode 100644 index 000000000000..0a6d8d961265 --- /dev/null +++ b/app/Utils/CssInlinerPlugin.php @@ -0,0 +1,140 @@ +converter = new CssToInlineStyles(); + $this->options = $options; + } + + /** + * @param \Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(\Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + + if ($message->getContentType() === 'text/html' + || ($message->getContentType() === 'multipart/alternative' && $message->getBody()) + || ($message->getContentType() === 'multipart/mixed' && $message->getBody()) + ) { + [$body, $cssResources] = $this->messageSieve($message->getBody()); + $css = $this->concatCss($cssResources); + $message->setBody($this->converter->convert($body, $css)); + } + + foreach ($message->getChildren() as $part) { + if (strpos($part->getContentType(), 'text/html') === 0) { + [$body, $cssResources] = $this->messageSieve($part->getBody()); + $css = $this->concatCss($cssResources); + $part->setBody($this->converter->convert($body, $css)); + } + } + } + + /** + * Do nothing + * + * @param \Swift_Events_SendEvent $evt + */ + public function sendPerformed(\Swift_Events_SendEvent $evt) + { + // Do Nothing + } + + protected function concatCss(array $cssResources): string + { + $output = ''; + foreach ($cssResources as $cssResource) { + $output.= $this->fetchCss($cssResource); + } + + return $output; + } + + protected function fetchCss(string $filename): string + { + if (isset($this->cssCache[$filename])) { + return $this->cssCache[$filename]; + } + + $fixedFilename = $filename; + // Fix relative protocols on hrefs. Assume https. + if (substr($filename, 0, 2) === '//') { + $fixedFilename = 'https:' . $filename; + } + + $content = file_get_contents($fixedFilename); + if (! $content) { + return ''; + } + + $this->cssCache[$filename] = $content; + + return $content; + } + + protected function messageSieve(string $message): array + { + $cssResources = []; + + // Initialize with config defaults, if any + if (isset($this->options['css-files'])) { + $cssResources = $this->options['css-files']; + } + + $dom = new \DOMDocument(); + // set error level + $internalErrors = libxml_use_internal_errors(true); + + $dom->loadHTML($message); + + // Restore error level + libxml_use_internal_errors($internalErrors); + $link_tags = $dom->getElementsByTagName('link'); + + /** @var \DOMElement $link */ + foreach ($link_tags as $link) { + if ($link->getAttribute('rel') === 'stylesheet') { + array_push($cssResources, $link->getAttribute('href')); + } + } + + $link_tags = $dom->getElementsByTagName('link'); + for ($i = $link_tags->length; --$i >= 0;) { + $link = $link_tags->item($i); + if ($link->getAttribute('rel') === 'stylesheet') { + $link->parentNode->removeChild($link); + } + } + + if (count($cssResources)) { + return [$dom->saveHTML(), $cssResources]; + } + + return [$message, []]; + } +} diff --git a/config/app.php b/config/app.php index b5a5b1bbabe7..79240cf39cdc 100644 --- a/config/app.php +++ b/config/app.php @@ -181,7 +181,7 @@ return [ App\Providers\MultiDBProvider::class, App\Providers\ClientPortalServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class, - + App\Providers\MailCssInlinerServiceProvider::class, ], /* diff --git a/config/css-inliner.php b/config/css-inliner.php new file mode 100644 index 000000000000..b45f64c3c8e8 --- /dev/null +++ b/config/css-inliner.php @@ -0,0 +1,18 @@ + [], + +];