Skip to content

Commit 3b4d0f0

Browse files
felixschndrbackportbot[bot]
authored andcommitted
fix: embed Nextcloud logo directly into SMTP notifications
Signed-off-by: Felix Schneider <mail@fschneider.me>
1 parent f67d6e6 commit 3b4d0f0

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
@@ -295,6 +295,16 @@ public function getLogo($useSvg = true): string {
295295
return $this->urlGenerator->linkToRoute('theming.Theming.getImage', [ 'key' => 'logo', 'useSvg' => $useSvg, 'v' => $cacheBusterCounter ]);
296296
}
297297

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

lib/private/Mail/EMailTemplate.php

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

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

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

361380
/**

lib/private/Mail/Message.php

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

lib/private/legacy/OC_Defaults.php

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

323+
/**
324+
* Raw logo image data (raster, not SVG) for embedding directly into emails,
325+
* so mail clients don't have to fetch it from the internet.
326+
*
327+
* @return array{content: string, mimeType: string}|null null when unavailable
328+
*/
329+
public function getLogoImage(): ?array {
330+
if ($this->themeExist('getLogoImage')) {
331+
return $this->theme->getLogoImage();
332+
}
333+
334+
$content = @file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
335+
if ($content === false) {
336+
return null;
337+
}
338+
return ['content' => $content, 'mimeType' => 'image/png'];
339+
}
340+
323341
public function getTextColorPrimary() {
324342
if ($this->themeExist('getTextColorPrimary')) {
325343
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
@@ -219,4 +219,20 @@ public function testEMailTemplateAlternativePlainTexts(): void {
219219
$expectedTXT = file_get_contents(\OC::$SERVERROOT . '/tests/data/emails/new-account-email-custom-text-alternative.txt');
220220
$this->assertSame($expectedTXT, $this->emailTemplate->renderText());
221221
}
222+
223+
public function testEMailTemplateEmbedsLogo(): void {
224+
$this->defaults->method('getDefaultColorPrimary')->willReturn('#0082c9');
225+
$this->defaults->method('getName')->willReturn('TestCloud');
226+
$this->defaults->method('getLogoImage')
227+
->willReturn(['content' => 'PNGDATA', 'mimeType' => 'image/png']);
228+
$this->urlGenerator->expects($this->never())->method('getAbsoluteURL');
229+
230+
$this->emailTemplate->addHeader();
231+
232+
$this->assertStringContainsString('src="cid:logo"', $this->emailTemplate->renderHtml());
233+
$this->assertSame(
234+
[['name' => 'logo', 'content' => 'PNGDATA', 'mimeType' => 'image/png']],
235+
$this->emailTemplate->getInlineImages()
236+
);
237+
}
222238
}

0 commit comments

Comments
 (0)