Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/MailPanel.body.latte
Original file line number Diff line number Diff line change
@@ -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}
16 changes: 6 additions & 10 deletions src/MailPanel.latte
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
</tr>

<tr class="nextras-mail-panel-preview-html tracy-collapsed">
<td colspan="2" data-content="{$message|previewHtml}"></td>
<td colspan="2" data-src="{link('preview', ['message-id' => $messageId])}"></td>
</tr>
</table>
{/foreach}
Expand All @@ -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);
};
Expand Down
48 changes: 46 additions & 2 deletions src/MailPanel.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,11 @@
$body = $part->getBody();
$transferEncoding = strtolower((string) $part->getHeader('Content-Transfer-Encoding'));

if ($transferEncoding === MimePart::EncodingQuotedPrintable) {

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.4 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.1 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.2 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.3 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.4

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.2

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.

Check failure on line 220 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.1

Access to undefined constant Nette\Mail\MimePart::EncodingQuotedPrintable.
return quoted_printable_decode($body);
}

if ($transferEncoding === MimePart::EncodingBase64) {

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.4 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.1 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.2 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.3 --prefer-lowest

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.4

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.2

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.

Check failure on line 224 in src/MailPanel.php

View workflow job for this annotation

GitHub Actions / PHP 7.1

Access to undefined constant Nette\Mail\MimePart::EncodingBase64.
$decodedBody = base64_decode($body, true);
return is_string($decodedBody) ? $decodedBody : $body;
}
Expand All @@ -240,7 +240,10 @@
$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)) {
Expand All @@ -264,7 +267,18 @@
$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;
}

Expand All @@ -280,6 +294,36 @@
}


private function renderMessagePreview(MimePart $message): string
{
return $this->getLatte()->renderToString(__DIR__ . '/MailPanel.body.latte', ['message' => $message]);
}


private function addParentBaseTarget(string $html): string
{
$baseTag = '<base target="_parent">';

if (preg_match('~<base(?:\s[^>]*)?>~i', $html) === 1) {
return $html;
}

if (preg_match('~<head(?:\s[^>]*)?>~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('~<html(?:\s[^>]*)?>~i', $html, $match, PREG_OFFSET_CAPTURE) === 1) {
$htmlTag = $match[0][0];
$position = $match[0][1] + strlen($htmlTag);
return substr($html, 0, $position) . '<head>' . $baseTag . '</head>' . substr($html, $position);
}

return '<!doctype html><html><head>' . $baseTag . '</head><body>' . $html . '</body></html>';
}


private function handleAttachment(string $messageId, int $attachmentId): void
{
assert($this->mailer !== null);
Expand Down
29 changes: 27 additions & 2 deletions tests/MailPanelTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class MailPanelTest extends TestCase
}


public function testPanelStoresEscapedHtmlPreviewInDataAttribute(): void
public function testPanelStoresPreviewEndpointInDataAttribute(): void
{
$message = (new Message())
->setSubject('Panel preview')
Expand All @@ -42,10 +42,22 @@ class MailPanelTest extends TestCase
$panel = $this->createPanel(new ArrayPersistentMailer(['message-id' => $message]));
$output = $panel->getPanel();

Assert::contains('data-content="&lt;h1&gt;Hello from HTML&lt;/h1&gt;"', $output);
Assert::contains('data-src="index.php?nextras-mail-panel-action=preview&amp;nextras-mail-panel-message-id=message-id"', $output);
}


public function testPreviewPreservesExistingBaseTag(): void
{
$message = (new Message())
->setSubject('Panel preview')
->setHtmlBody('<!doctype html><html><head><base href="https://example.com/assets/"><style>body{color:#000;}</style></head><body><img src="logo.png"></body></html>');

$output = $this->renderPreviewHtml($message);

Assert::contains('<base href="https://example.com/assets/">', $output);
Assert::notContains('<base target="_parent">', $output);
}

private function renderMessageBody(Message $message): string
{
$panel = $this->createPanel(new NullPersistentMailer());
Expand All @@ -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(
Expand Down
Loading