]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(mail): Fix big logos in mail templates for Outlook 46419/head
authorJoas Schilling <coding@schilljs.com>
Wed, 10 Jul 2024 13:29:45 +0000 (15:29 +0200)
committerJoas Schilling <coding@schilljs.com>
Wed, 17 Jul 2024 07:24:55 +0000 (09:24 +0200)
Signed-off-by: Joas Schilling <coding@schilljs.com>
13 files changed:
apps/settings/tests/Mailer/NewUserMailHelperTest.php
apps/theming/lib/ImageManager.php
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Mail/EMailTemplate.php
lib/private/Mail/Mailer.php
lib/private/Repair.php
lib/private/Repair/RepairLogoDimension.php [new file with mode: 0644]
tests/data/emails/new-account-email-custom.html
tests/data/emails/new-account-email-single-button.html
tests/data/emails/new-account-email.html
tests/lib/Mail/EMailTemplateTest.php
tests/lib/Mail/MailerTest.php

index 19cfab8c19346b16cfeff23a6d702eab223997dc..72af678f832e83f1b1c7e9a29fdf26f2a13ec277 100644 (file)
@@ -57,6 +57,8 @@ class NewUserMailHelperTest extends TestCase {
                        $this->defaults,
                        $this->urlGenerator,
                        $this->l10nFactory,
+                       null,
+                       null,
                        'test.TestTemplate',
                        []
                );
index 761b0c9a8ba83f699e0b6c440593078dfe757644..4afdedf51896b21dc23a883a51f77dc0dea7b526 100644 (file)
@@ -194,6 +194,10 @@ class ImageManager {
                } catch (NotFoundException $e) {
                } catch (NotPermittedException $e) {
                }
+
+               if ($key === 'logo') {
+                       $this->config->deleteAppValue('theming', 'logoDimensions');
+               }
        }
 
        public function updateImage(string $key, string $tmpFile): string {
@@ -266,6 +270,25 @@ class ImageManager {
 
                $target->putContent(file_get_contents($tmpFile));
 
+               if ($key === 'logo') {
+                       $content = file_get_contents($tmpFile);
+                       $newImage = @imagecreatefromstring($content);
+                       if ($newImage !== false) {
+                               $this->config->setAppValue('theming', 'logoDimensions', imagesx($newImage) . 'x' . imagesy($newImage));
+                       } elseif (str_starts_with($detectedMimeType, 'image/svg')) {
+                               $matched = preg_match('/viewbox=["\']\d* \d* (\d*\.?\d*) (\d*\.?\d*)["\']/i', $content, $matches);
+                               if ($matched) {
+                                       $this->config->setAppValue('theming', 'logoDimensions', $matches[1] . 'x' . $matches[2]);
+                               } else {
+                                       $this->logger->warning('Could not read logo image dimensions to optimize for mail header');
+                                       $this->config->deleteAppValue('theming', 'logoDimensions');
+                               }
+                       } else {
+                               $this->logger->warning('Could not read logo image dimensions to optimize for mail header');
+                               $this->config->deleteAppValue('theming', 'logoDimensions');
+                       }
+               }
+
                return $detectedMimeType;
        }
 
index 3f0ce230fec67058e91f38afb0113cc43cd4d36b..b0b60b3e0bbb752e4ea6e5acffb6d6ed36f330f8 100644 (file)
@@ -1757,6 +1757,7 @@ return array(
     'OC\\Repair\\RemoveLinkShares' => $baseDir . '/lib/private/Repair/RemoveLinkShares.php',
     'OC\\Repair\\RepairDavShares' => $baseDir . '/lib/private/Repair/RepairDavShares.php',
     'OC\\Repair\\RepairInvalidShares' => $baseDir . '/lib/private/Repair/RepairInvalidShares.php',
+    'OC\\Repair\\RepairLogoDimension' => $baseDir . '/lib/private/Repair/RepairLogoDimension.php',
     'OC\\Repair\\RepairMimeTypes' => $baseDir . '/lib/private/Repair/RepairMimeTypes.php',
     'OC\\RichObjectStrings\\Validator' => $baseDir . '/lib/private/RichObjectStrings/Validator.php',
     'OC\\Route\\CachingRouter' => $baseDir . '/lib/private/Route/CachingRouter.php',
index bc23e72ab3c23a0662c96a6bb6330fd378f4533c..648b736bb77845e67d5402c80d5fd4a89c1f0129 100644 (file)
@@ -1790,6 +1790,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\Repair\\RemoveLinkShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveLinkShares.php',
         'OC\\Repair\\RepairDavShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairDavShares.php',
         'OC\\Repair\\RepairInvalidShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairInvalidShares.php',
+        'OC\\Repair\\RepairLogoDimension' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairLogoDimension.php',
         'OC\\Repair\\RepairMimeTypes' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairMimeTypes.php',
         'OC\\RichObjectStrings\\Validator' => __DIR__ . '/../../..' . '/lib/private/RichObjectStrings/Validator.php',
         'OC\\Route\\CachingRouter' => __DIR__ . '/../../..' . '/lib/private/Route/CachingRouter.php',
index 80740e14acacc57fe51d6acc9a8499b50e4f73d6..cbb006203e138bf5bdcab5de2bac0519a21d1bc5 100644 (file)
@@ -76,7 +76,7 @@ EOF;
                                                        <tbody>
                                                        <tr style="padding:0;text-align:left;vertical-align:top">
                                                                <center data-parsed="" style="background-color:%s;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px">
-                                                                       <img class="logo float-center" src="%s" alt="%s" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto">
+                                                                       <img class="logo float-center" src="%s" alt="%s" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto"%s>
                                                                </center>
                                                        </tr>
                                                        </tbody>
@@ -308,6 +308,8 @@ EOF;
                protected Defaults $themingDefaults,
                protected IURLGenerator $urlGenerator,
                protected IFactory $l10nFactory,
+               protected ?int $logoWidth,
+               protected ?int $logoHeight,
                protected string $emailId,
                protected array $data,
        ) {
@@ -330,8 +332,14 @@ EOF;
                }
                $this->headerAdded = true;
 
+               $logoSizeDimensions = '';
+               if ($this->logoWidth && $this->logoHeight) {
+                       // Provide a logo size when we have the dimensions so that it displays nicely in Outlook
+                       $logoSizeDimensions = ' width="' . $this->logoWidth . '" height="' . $this->logoHeight . '"';
+               }
+
                $logoUrl = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo(false));
-               $this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getDefaultColorPrimary(), $logoUrl, $this->themingDefaults->getName()]);
+               $this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getDefaultColorPrimary(), $logoUrl, $this->themingDefaults->getName(), $logoSizeDimensions]);
        }
 
        /**
index e866cbfdbbc343959c63ebe11231dafe16c28493..4ddb748fc26d4fc15dcc2f9741b43fe0771e46a7 100644 (file)
@@ -52,6 +52,12 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
  * @package OC\Mail
  */
 class Mailer implements IMailer {
+       // Do not move this block or change it's content without contacting the release crew
+       public const DEFAULT_DIMENSIONS = '252x120';
+       // Do not move this block or change it's content without contacting the release crew
+
+       public const MAX_LOGO_SIZE = 105;
+
        private ?MailerInterface $instance = null;
 
        public function __construct(
@@ -109,10 +115,37 @@ class Mailer implements IMailer {
                        );
                }
 
+               $logoDimensions = $this->config->getAppValue('theming', 'logoDimensions', self::DEFAULT_DIMENSIONS);
+               if (str_contains($logoDimensions, 'x')) {
+                       [$width, $height] = explode('x', $logoDimensions);
+                       $width = (int) $width;
+                       $height = (int) $height;
+
+                       if ($width > self::MAX_LOGO_SIZE || $height > self::MAX_LOGO_SIZE) {
+                               if ($width === $height) {
+                                       $logoWidth = self::MAX_LOGO_SIZE;
+                                       $logoHeight = self::MAX_LOGO_SIZE;
+                               } elseif ($width > $height) {
+                                       $logoWidth = self::MAX_LOGO_SIZE;
+                                       $logoHeight = (int) (($height / $width) * self::MAX_LOGO_SIZE);
+                               } else {
+                                       $logoWidth = (int) (($width / $height) * self::MAX_LOGO_SIZE);
+                                       $logoHeight = self::MAX_LOGO_SIZE;
+                               }
+                       } else {
+                               $logoWidth = $width;
+                               $logoHeight = $height;
+                       }
+               } else {
+                       $logoWidth = $logoHeight = null;
+               }
+
                return new EMailTemplate(
                        $this->defaults,
                        $this->urlGenerator,
                        $this->l10nFactory,
+                       $logoWidth,
+                       $logoHeight,
                        $emailId,
                        $data
                );
index ed9cd400f617a2b4d7b85387c13992759d9c90f1..a41c6c5277e00cd223a8c6cb10b5ab31689dd6f8 100644 (file)
@@ -52,6 +52,7 @@ use OC\Repair\Owncloud\UpdateLanguageCodes;
 use OC\Repair\RemoveLinkShares;
 use OC\Repair\RepairDavShares;
 use OC\Repair\RepairInvalidShares;
+use OC\Repair\RepairLogoDimension;
 use OC\Repair\RepairMimeTypes;
 use OC\Template\JSCombiner;
 use OCA\DAV\Migration\DeleteSchedulingObjects;
@@ -187,6 +188,7 @@ class Repair implements IOutput {
                        \OCP\Server::get(AddRemoveOldTasksBackgroundJob::class),
                        \OCP\Server::get(AddMetadataGenerationJob::class),
                        \OCP\Server::get(AddAppConfigLazyMigration::class),
+                       \OCP\Server::get(RepairLogoDimension::class),
                ];
        }
 
diff --git a/lib/private/Repair/RepairLogoDimension.php b/lib/private/Repair/RepairLogoDimension.php
new file mode 100644 (file)
index 0000000..122da20
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Repair;
+
+use OCA\Theming\ImageManager;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+use OCP\Server;
+
+class RepairLogoDimension implements IRepairStep {
+       public function __construct(
+               protected IConfig $config,
+       ) {
+       }
+
+       public function getName(): string {
+               return 'Cache logo dimension to fix size in emails on Outlook';
+       }
+
+       public function run(IOutput $output): void {
+               $logoDimensions = $this->config->getAppValue('theming', 'logoDimensions');
+               if (preg_match('/^\d+x\d+$/', $logoDimensions)) {
+                       $output->info('Logo dimensions are already known');
+                       return;
+               }
+
+               try {
+                       /** @var ImageManager $imageManager */
+                       $imageManager = Server::get(ImageManager::class);
+               } catch (\Throwable) {
+                       $output->info('Theming is disabled');
+                       return;
+               }
+
+               if (!$imageManager->hasImage('logo')) {
+                       $output->info('Theming is not used to provide a logo');
+                       return;
+               }
+
+               $simpleFile = $imageManager->getImage('logo', false);
+
+               $image = @imagecreatefromstring($simpleFile->getContent());
+
+               $dimensions = '';
+               if ($image !== false) {
+                       $dimensions = imagesx($image) . 'x' . imagesy($image);
+               } elseif (str_starts_with($simpleFile->getMimeType(), 'image/svg')) {
+                       $matched = preg_match('/viewbox=["\']\d* \d* (\d*\.?\d*) (\d*\.?\d*)["\']/i', $simpleFile->getContent(), $matches);
+                       if ($matched) {
+                               $dimensions = $matches[1] . 'x' . $matches[2];
+                       }
+               }
+
+               if (!$dimensions) {
+                       $output->warning('Failed to read dimensions from logo');
+                       $this->config->deleteAppValue('theming', 'logoDimensions');
+                       return;
+               }
+
+               $dimensions = imagesx($image) . 'x' . imagesy($image);
+               $this->config->setAppValue('theming', 'logoDimensions', $dimensions);
+               $output->info('Updated logo dimensions: ' . $dimensions);
+       }
+}
index e1c9fd4fb4f8167e956651300883249852f56641..370a2f749d74e322a0742be0d05b604b6a01480f 100644 (file)
@@ -23,7 +23,7 @@
                                                        <tbody>
                                                        <tr style="padding:0;text-align:left;vertical-align:top">
                                                                <center data-parsed="" style="background-color:#0082c9;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px">
-                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto">
+                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto" width="252" height="120">
                                                                </center>
                                                        </tr>
                                                        </tbody>
index 35ad9b14a1c0f0c2d4b4c6b7e76c632a77f6b512..51ed5b16f66a64cdd44627f7969bc0b791bafe35 100644 (file)
@@ -23,7 +23,7 @@
                                                        <tbody>
                                                        <tr style="padding:0;text-align:left;vertical-align:top">
                                                                <center data-parsed="" style="background-color:#0082c9;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px">
-                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto">
+                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto" width="252" height="120">
                                                                </center>
                                                        </tr>
                                                        </tbody>
index e879f44120676f6ca003aff749a8bf34dce24bc1..e45edba74eea03ca33b3af6cf600573aa545ef95 100644 (file)
@@ -23,7 +23,7 @@
                                                        <tbody>
                                                        <tr style="padding:0;text-align:left;vertical-align:top">
                                                                <center data-parsed="" style="background-color:#0082c9;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px">
-                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto">
+                                                                       <img class="logo float-center" src="https://example.org/img/logo-mail-header.png" alt="TestCloud" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto" width="252" height="120">
                                                                </center>
                                                        </tr>
                                                        </tbody>
index a12cf82ab50e7bfe8e4191eee0ea94f106140856..45ccf543848184c2a27343f23ed49076ead6ec10 100644 (file)
@@ -38,6 +38,8 @@ class EMailTemplateTest extends TestCase {
                        $this->defaults,
                        $this->urlGenerator,
                        $this->l10n,
+                       252,
+                       120,
                        'test.TestTemplate',
                        []
                );
index 91006a8331abac385a554df355c67a09bcd77b23..be95307da6d2e2337392b5ccf1e2141876e73df1 100644 (file)
@@ -236,6 +236,9 @@ class MailerTest extends TestCase {
                $this->config->method('getSystemValueString')
                        ->with('mail_template_class', '')
                        ->willReturnArgument(1);
+               $this->config->method('getAppValue')
+                       ->with('theming', 'logoDimensions', Mailer::DEFAULT_DIMENSIONS)
+                       ->willReturn(Mailer::DEFAULT_DIMENSIONS);
 
                $this->assertSame(EMailTemplate::class, get_class($this->mailer->createEMailTemplate('tests.MailerTest')));
        }