Skip to content

Commit 7577f10

Browse files
committed
fix: embed Nextcloud logo directly into SMTP notifications
Signed-off-by: Felix Schneider <mail@fschneider.me>
1 parent 4ad68f0 commit 7577f10

6 files changed

Lines changed: 80 additions & 2 deletions

File tree

apps/theming/lib/ThemingDefaults.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,16 @@ public function getLogo($useSvg = true): string {
296296
return $this->urlGenerator->linkToRoute('theming.Theming.getImage', [ 'key' => 'logo', 'useSvg' => $useSvg, 'v' => $cacheBusterCounter ]);
297297
}
298298

299+
#[\Override]
300+
public function getLogoImage(): ?array {
301+
try {
302+
$file = $this->imageManager->getImage('logo', false);
303+
return ['content' => $file->getContent(), 'mimeType' => $file->getMimeType()];
304+
} catch (\Exception $e) {
305+
return parent::getLogoImage();
306+
}
307+
}
308+
299309
/**
300310
* Themed background image url
301311
*

lib/private/Mail/EMailTemplate.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class EMailTemplate implements IEMailTemplate {
3434
protected bool $bodyListOpened = false;
3535
/** indicated if the footer is added */
3636
protected bool $footerAdded = false;
37+
/** @var array<array{name: string, content: string, mimeType: string}> images to embed inline, referenced via cid: */
38+
protected array $inlineImages = [];
3739

3840
protected string $head = <<<EOF
3941
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -355,8 +357,25 @@ public function addHeader(): void {
355357
$logoSizeDimensions = ' width="' . $this->logoWidth . '" height="' . $this->logoHeight . '"';
356358
}
357359

358-
$logoUrl = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo(false));
359-
$this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getDefaultColorPrimary(), $logoUrl, $this->themingDefaults->getName(), $logoSizeDimensions]);
360+
$logoImage = $this->themingDefaults->getLogoImage();
361+
if ($logoImage !== null) {
362+
// Embed the logo directly in the message instead of linking to it, so mail
363+
// clients don't have to fetch it from the internet (some (e.g. gmail) block that).
364+
$logoSrc = 'cid:logo';
365+
$this->inlineImages[] = ['name' => 'logo'] + $logoImage;
366+
} else {
367+
$logoSrc = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo(false));
368+
}
369+
$this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getDefaultColorPrimary(), $logoSrc, $this->themingDefaults->getName(), $logoSizeDimensions]);
370+
}
371+
372+
/**
373+
* Images that must be embedded inline in the message, referenced via `cid:<name>` in the HTML body
374+
*
375+
* @return array<array{name: string, content: string, mimeType: string}>
376+
*/
377+
public function getInlineImages(): array {
378+
return $this->inlineImages;
360379
}
361380

362381
/**

lib/private/Mail/Message.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ public function useTemplate(IEMailTemplate $emailTemplate): IMessage {
292292
$this->setPlainBody($emailTemplate->renderText());
293293
if (!$this->plainTextOnly) {
294294
$this->setHtmlBody($emailTemplate->renderHtml());
295+
if ($emailTemplate instanceof EMailTemplate) {
296+
foreach ($emailTemplate->getInlineImages() as $image) {
297+
$this->attachInline($image['content'], $image['name'], $image['mimeType']);
298+
}
299+
}
295300
}
296301
return $this;
297302
}

lib/private/legacy/OC_Defaults.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,24 @@ public function getLogo($useSvg = true) {
321321
return $logo . '?v=' . hash('sha1', implode('.', Util::getVersion()));
322322
}
323323

324+
/**
325+
* Raw logo image data (raster, not SVG) for embedding directly into emails,
326+
* so mail clients don't have to fetch it from the internet.
327+
*
328+
* @return array{content: string, mimeType: string}|null null when unavailable
329+
*/
330+
public function getLogoImage(): ?array {
331+
if ($this->themeExist('getLogoImage')) {
332+
return $this->theme->getLogoImage();
333+
}
334+
335+
$content = @file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
336+
if ($content === false) {
337+
return null;
338+
}
339+
return ['content' => $content, 'mimeType' => 'image/png'];
340+
}
341+
324342
public function getTextColorPrimary() {
325343
if ($this->themeExist('getTextColorPrimary')) {
326344
return $this->theme->getTextColorPrimary();

lib/public/Defaults.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ public function getLogo(bool $useSvg = true): string {
173173
return $this->defaults->getLogo($useSvg);
174174
}
175175

176+
/**
177+
* Raw logo image data (raster) for embedding directly into emails
178+
*
179+
* @return array{content: string, mimeType: string}|null null when unavailable
180+
* @since 35.0.0
181+
*/
182+
public function getLogoImage(): ?array {
183+
return $this->defaults->getLogoImage();
184+
}
185+
176186
/**
177187
* Returns primary color
178188
* @return string

tests/lib/Mail/EMailTemplateTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,20 @@ public function testEMailTemplateAlternativePlainTexts(): void {
217217
$expectedTXT = file_get_contents(\OC::$SERVERROOT . '/tests/data/emails/new-account-email-custom-text-alternative.txt');
218218
$this->assertSame($expectedTXT, $this->emailTemplate->renderText());
219219
}
220+
221+
public function testEMailTemplateEmbedsLogo(): void {
222+
$this->defaults->method('getDefaultColorPrimary')->willReturn('#0082c9');
223+
$this->defaults->method('getName')->willReturn('TestCloud');
224+
$this->defaults->method('getLogoImage')
225+
->willReturn(['content' => 'PNGDATA', 'mimeType' => 'image/png']);
226+
$this->urlGenerator->expects($this->never())->method('getAbsoluteURL');
227+
228+
$this->emailTemplate->addHeader();
229+
230+
$this->assertStringContainsString('src="cid:logo"', $this->emailTemplate->renderHtml());
231+
$this->assertSame(
232+
[['name' => 'logo', 'content' => 'PNGDATA', 'mimeType' => 'image/png']],
233+
$this->emailTemplate->getInlineImages()
234+
);
235+
}
220236
}

0 commit comments

Comments
 (0)