diff --git a/src/MailPanel.body.latte b/src/MailPanel.body.latte index 5e65711..0bc62f0 100644 --- a/src/MailPanel.body.latte +++ b/src/MailPanel.body.latte @@ -1,2 +1,2 @@ -{* Little magic here. Create iframe and then render message into it (needed because HTML messages) *} +{* Render a standalone HTML preview document or fragment. *} {$message|previewHtml|noescape} diff --git a/src/MailPanel.latte b/src/MailPanel.latte index 158fef0..68c10cd 100644 --- a/src/MailPanel.latte +++ b/src/MailPanel.latte @@ -108,7 +108,7 @@ - + {/foreach} @@ -129,22 +129,18 @@ var iframe = document.createElement('iframe'); preview.appendChild(iframe); - iframe.contentWindow.document.write(preview.dataset.content); - iframe.contentWindow.document.close(); - delete preview.dataset.content; - - var baseTag = iframe.contentWindow.document.createElement('base'); - baseTag.target = '_parent'; - iframe.contentWindow.document.body.appendChild(baseTag); - var fixHeight = function (ev) { + var iframeDocument = iframe.contentWindow.document; + var iframeBody = iframeDocument.body || iframeDocument.documentElement; iframe.style.height = 0; - iframe.style.height = iframe.contentWindow.document.body.scrollHeight + 'px'; + iframe.style.height = iframeBody.scrollHeight + 'px'; iframe.contentWindow.removeEventListener(ev.type, fixHeight); }; iframe.contentWindow.addEventListener('load', fixHeight); iframe.contentWindow.addEventListener('resize', fixHeight); + iframe.src = preview.dataset.src; + delete preview.dataset.src; actions.removeEventListener('tracy-toggle', initHtmlPreview); actions.removeEventListener('click', initHtmlPreview); }; diff --git a/src/MailPanel.php b/src/MailPanel.php index a6b4f1a..a701f0b 100644 --- a/src/MailPanel.php +++ b/src/MailPanel.php @@ -240,7 +240,10 @@ private function tryHandleRequest(): void $messageId = $this->request->getQuery('nextras-mail-panel-message-id'); $attachmentId = $this->request->getQuery('nextras-mail-panel-attachment-id'); - if ($action === 'detail' && is_string($messageId)) { + if ($action === 'preview' && is_string($messageId)) { + $this->handlePreview($messageId); + + } elseif ($action === 'detail' && is_string($messageId)) { $this->handleDetail($messageId); } elseif ($action === 'source' && is_string($messageId)) { @@ -264,7 +267,18 @@ private function handleDetail(string $messageId): void $message = $this->mailer->getMessage($messageId); header('Content-Type: text/html'); - $this->getLatte()->render(__DIR__ . '/MailPanel.body.latte', ['message' => $message]); + echo $this->renderMessagePreview($message); + exit; + } + + + private function handlePreview(string $messageId): void + { + assert($this->mailer !== null); + $message = $this->mailer->getMessage($messageId); + + header('Content-Type: text/html'); + echo $this->addParentBaseTarget($this->renderMessagePreview($message)); exit; } @@ -280,6 +294,36 @@ private function handleSource(string $messageId): void } + private function renderMessagePreview(MimePart $message): string + { + return $this->getLatte()->renderToString(__DIR__ . '/MailPanel.body.latte', ['message' => $message]); + } + + + private function addParentBaseTarget(string $html): string + { + $baseTag = ''; + + if (preg_match('~]*)?>~i', $html) === 1) { + return $html; + } + + if (preg_match('~]*)?>~i', $html, $match, PREG_OFFSET_CAPTURE) === 1) { + $headTag = $match[0][0]; + $position = $match[0][1] + strlen($headTag); + return substr($html, 0, $position) . $baseTag . substr($html, $position); + } + + if (preg_match('~]*)?>~i', $html, $match, PREG_OFFSET_CAPTURE) === 1) { + $htmlTag = $match[0][0]; + $position = $match[0][1] + strlen($htmlTag); + return substr($html, 0, $position) . '' . $baseTag . '' . substr($html, $position); + } + + return '' . $baseTag . '' . $html . ''; + } + + private function handleAttachment(string $messageId, int $attachmentId): void { assert($this->mailer !== null); diff --git a/tests/MailPanelTest.phpt b/tests/MailPanelTest.phpt index 452c096..f4a710d 100644 --- a/tests/MailPanelTest.phpt +++ b/tests/MailPanelTest.phpt @@ -33,7 +33,7 @@ class MailPanelTest extends TestCase } - public function testPanelStoresEscapedHtmlPreviewInDataAttribute(): void + public function testPanelStoresPreviewEndpointInDataAttribute(): void { $message = (new Message()) ->setSubject('Panel preview') @@ -42,10 +42,22 @@ class MailPanelTest extends TestCase $panel = $this->createPanel(new ArrayPersistentMailer(['message-id' => $message])); $output = $panel->getPanel(); - Assert::contains('data-content="<h1>Hello from HTML</h1>"', $output); + Assert::contains('data-src="index.php?nextras-mail-panel-action=preview&nextras-mail-panel-message-id=message-id"', $output); } + public function testPreviewPreservesExistingBaseTag(): void + { + $message = (new Message()) + ->setSubject('Panel preview') + ->setHtmlBody(''); + + $output = $this->renderPreviewHtml($message); + + Assert::contains('', $output); + Assert::notContains('', $output); + } + private function renderMessageBody(Message $message): string { $panel = $this->createPanel(new NullPersistentMailer()); @@ -58,6 +70,19 @@ class MailPanelTest extends TestCase } + private function renderPreviewHtml(Message $message): string + { + $panel = $this->createPanel(new NullPersistentMailer()); + + $renderMessagePreview = new ReflectionMethod(MailPanel::class, 'renderMessagePreview'); + $renderMessagePreview->setAccessible(true); + $addParentBaseTarget = new ReflectionMethod(MailPanel::class, 'addParentBaseTarget'); + $addParentBaseTarget->setAccessible(true); + + return $addParentBaseTarget->invoke($panel, $renderMessagePreview->invoke($panel, $message)); + } + + private function createPanel(IPersistentMailer $mailer): MailPanel { return new MailPanel(