From 4d1d768ee8c2cc015886c39d4dc21db8a39794b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Zral=C3=BD?= Date: Thu, 9 Apr 2026 14:50:24 +0200 Subject: [PATCH 1/2] fixed html preview loading --- src/MailPanel.body.latte | 2 +- src/MailPanel.latte | 16 ++++++--------- src/MailPanel.php | 44 ++++++++++++++++++++++++++++++++++++++-- tests/MailPanelTest.phpt | 5 ++--- 4 files changed, 51 insertions(+), 16 deletions(-) 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..f184eba 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,32 @@ 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, $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..2ce31ee 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,9 @@ 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); } - private function renderMessageBody(Message $message): string { $panel = $this->createPanel(new NullPersistentMailer()); From 8791f0f850393a9b4bd6322f4a2263ae1aee9407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Zral=C3=BD?= Date: Thu, 9 Apr 2026 16:29:50 +0200 Subject: [PATCH 2/2] fixed base tag handling --- src/MailPanel.php | 4 ++++ tests/MailPanelTest.phpt | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/MailPanel.php b/src/MailPanel.php index f184eba..a701f0b 100644 --- a/src/MailPanel.php +++ b/src/MailPanel.php @@ -304,6 +304,10 @@ 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); diff --git a/tests/MailPanelTest.phpt b/tests/MailPanelTest.phpt index 2ce31ee..f4a710d 100644 --- a/tests/MailPanelTest.phpt +++ b/tests/MailPanelTest.phpt @@ -45,6 +45,19 @@ class MailPanelTest extends TestCase 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()); @@ -57,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(