Fit wide images in user email notifications

This commit is contained in:
il2020
2026-09-30 13:36:18 +04:00
committed by GitHub
parent 78e987c920
commit c96b24a7da
3 changed files with 69 additions and 1 deletions

View File

@@ -1448,6 +1448,51 @@ class Mail
return $separator;
}
/**
* Make images fit into the email container so that wide images
* do not stretch the layout.
* - Scale down width/height attributes (Outlook ignores CSS max-width).
* - Add "max-width:100%; height:auto" inline styles.
*/
public static function fitImages($html, $max_width = 0)
{
if (!$html || stripos($html, '<img') === false) {
return $html;
}
return preg_replace_callback('#<img\b[^>]*>#i', function ($m) use ($max_width) {
$tag = $m[0];
// Scale down dimensions preserving aspect ratio.
if ($max_width
&& preg_match('#\swidth\s*=\s*["\']?(\d+)#i', $tag, $w)
&& (int)$w[1] > $max_width
) {
$width = (int)$w[1];
$tag = preg_replace('#(\swidth\s*=\s*["\']?)\d+#i', '${1}'.$max_width, $tag, 1);
if (preg_match('#\sheight\s*=\s*["\']?(\d+)#i', $tag, $h)) {
$height = (int)round((int)$h[1] * $max_width / $width);
$tag = preg_replace('#(\sheight\s*=\s*["\']?)\d+#i', '${1}'.$height, $tag, 1);
}
}
$fit_style = 'max-width:100%;height:auto;';
if (preg_match('#\sstyle\s*=\s*(["\'])(.*?)\1#is', $tag, $s)) {
// Remove existing max-width and height declarations.
$style = preg_replace('#(^|;)\s*(max-width|height)\s*:[^;]*#i', '$1', $s[2]);
$style = trim(preg_replace('#;(\s*;)+#', ';', $style), " \t\r\n;");
$style = ($style !== '' ? $style.';' : '').$fit_style;
$tag = str_replace($s[0], ' style='.$s[1].$style.$s[1], $tag);
} else {
$self_closing = preg_match('#/\s*>$#', $tag);
$tag = preg_replace('#\s*/?\s*>$#', '', $tag).' style="'.$fit_style.'"'.($self_closing ? ' />' : '>');
}
return $tag;
}, $html) ?: $html;
}
// Sanitize status message - remove SMTP username and password.
public static function sanitizeSmtpStatusMessage($status_message)
{

View File

@@ -4,6 +4,7 @@
<meta name="viewport" content="width=350px, user-scalable=yes">
<style>
#wrapper * { max-width: 650px !important; }
#wrapper img { max-width: 100% !important; height: auto !important; }
p { margin:0 0 1.5em 0; }
pre { font-family: Menlo, Monaco, monospace, sans-serif; padding: 0 0 1.6em 0; color:#333333; line-height:15px; }
a { color:#3f8abf; text-decoration:none; }
@@ -227,7 +228,7 @@
@endif
@action('email_notification.before_body', $thread, $user, $conversation)
<div style="font-family:Arial, 'Helvetica Neue', Helvetica, Tahoma, sans-serif; color:#444; font-size:14px; line-height:20px; margin:0; @if ($is_rtl) text-align: right; direction: rtl; unicode-bidi: plaintext; @endif">
{!! safe_raw_html($thread->getCleanBody() ?? '') !!}
{!! safe_raw_html(\MailHelper::fitImages($thread->getCleanBody() ?? '', 580)) !!}
</div>
@if ($thread->has_attachments)

View File

@@ -18,4 +18,26 @@ class MailTest extends TestCase
// self::assertSame('multipart/alternative;
// boundary="------------c141PQyZlgOaY0xUqE0ydJUA"', \MailHelper::getHeader($message, 'Content-Type'));
}
public function testFitImages(): void
{
// Wide image is scaled down preserving aspect ratio.
self::assertSame(
'<img src="a.png" alt="" width="580" height="326" style="max-width:100%;height:auto;">',
\MailHelper::fitImages('<img src="a.png" alt="" width="765" height="430">', 580)
);
// Narrow image keeps its dimensions.
self::assertSame(
'<p><img src="a.png" width="300" height="200" style="max-width:100%;height:auto;" /></p>',
\MailHelper::fitImages('<p><img src="a.png" width="300" height="200" /></p>', 580)
);
// Existing max-width and height are replaced, other styles are kept.
self::assertSame(
'<img src="a.png" style="width:765px; line-height:1; border:0;max-width:100%;height:auto;">',
\MailHelper::fitImages('<img src="a.png" style="width:765px; max-width:800px; line-height:1; height:430px; border:0">', 580)
);
// No images.
self::assertSame('<p>text</p>', \MailHelper::fitImages('<p>text</p>', 580));
self::assertSame('', \MailHelper::fitImages('', 580));
}
}