diff options
Diffstat (limited to 'lib/private/Mail')
-rw-r--r-- | lib/private/Mail/Attachment.php | 2 | ||||
-rw-r--r-- | lib/private/Mail/EMailTemplate.php | 78 | ||||
-rw-r--r-- | lib/private/Mail/Mailer.php | 65 | ||||
-rw-r--r-- | lib/private/Mail/Message.php | 4 | ||||
-rw-r--r-- | lib/private/Mail/Provider/Manager.php | 255 |
5 files changed, 360 insertions, 44 deletions
diff --git a/lib/private/Mail/Attachment.php b/lib/private/Mail/Attachment.php index 8dede9714f8..2a5246c0019 100644 --- a/lib/private/Mail/Attachment.php +++ b/lib/private/Mail/Attachment.php @@ -22,7 +22,7 @@ class Attachment implements IAttachment { private ?string $body, private ?string $name, private ?string $contentType, - private ?string $path = null + private ?string $path = null, ) { } diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php index 80740e14aca..a327109cc12 100644 --- a/lib/private/Mail/EMailTemplate.php +++ b/lib/private/Mail/EMailTemplate.php @@ -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> @@ -190,32 +190,46 @@ EOF; <tr style="padding:0;text-align:left;vertical-align:top"> <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"> <center data-parsed="" style="min-width:490px;width:100%%"> - <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;max-height:60px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;background-color:%1\$s;color:#fefefe;"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> + <!--[if (gte mso 9)|(IE)]> + <table> + <tr> + <td> + <![endif]--> + <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;background-color:%1\$s;color:#fefefe;"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;border-radius:2px;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;padding:10px 25px 10px 25px;text-align:left;outline:1px solid %6\$s;text-decoration:none">%7\$s</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid %6\$s;text-decoration:none">%7\$s</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> - </tr> - </table> - <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> + <td> + <![endif]--> + <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;background:#777;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="%8\$s" style="Margin:0;background-color:#fff;border:0 solid #777;border-radius:2px;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;outline:1px solid #CBCBCB;padding:10px 25px 10px 25px;text-align:left;text-decoration:none">%9\$s</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="%8\$s" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">%9\$s</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> </tr> </table> + <![endif]--> </center> </th> <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th> @@ -243,13 +257,13 @@ EOF; <tr style="padding:0;text-align:left;vertical-align:top"> <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"> <center data-parsed="" style="min-width:490px;width:100%%"> - <table class="button btn default primary float-center" style="Margin:0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0;max-height:60px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;color:#fefefe;background-color:%1\$s;"> + <table class="button btn default primary float-center" style="Margin:0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0;border-radius:8px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;color:#fefefe;background-color:%1\$s;"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;border-radius:2px;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;padding:10px 25px 10px 25px;text-align:left;outline:1px solid %5\$s;text-decoration:none">%7\$s</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid %5\$s;text-decoration:none">%7\$s</a> </td> </tr> </table> @@ -308,6 +322,8 @@ EOF; protected Defaults $themingDefaults, protected IURLGenerator $urlGenerator, protected IFactory $l10nFactory, + protected ?int $logoWidth, + protected ?int $logoHeight, protected string $emailId, protected array $data, ) { @@ -330,15 +346,21 @@ 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]); } /** * Adds a heading to the email * * @param string|bool $plainTitle Title that is used in the plain text email - * if empty the $title is used, if false none will be used + * if empty the $title is used, if false none will be used */ public function addHeading(string $title, $plainTitle = ''): void { if ($this->footerAdded) { @@ -371,7 +393,7 @@ EOF; * * @param string $text Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string|bool $plainText Text that is used in the plain text email - * if empty the $text is used, if false none will be used + * if empty the $text is used, if false none will be used */ public function addBodyText(string $text, $plainText = ''): void { if ($this->footerAdded) { @@ -398,9 +420,9 @@ EOF; * @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email * @param string $icon Absolute path, must be 16*16 pixels * @param string|bool $plainText Text that is used in the plain text email - * if empty or true the $text is used, if false none will be used + * if empty or true the $text is used, if false none will be used * @param string|bool $plainMetaInfo Meta info that is used in the plain text email - * if empty or true the $metaInfo is used, if false none will be used + * if empty or true the $metaInfo is used, if false none will be used * @param integer $plainIndent plainIndent If > 0, Indent plainText by this amount. * @since 12.0.0 */ @@ -417,7 +439,7 @@ EOF; if ($plainText === '' || $plainText === true) { $plainText = $text; $text = htmlspecialchars($text); - $text = str_replace("\n", "<br/>", $text); // convert newlines to HTML breaks + $text = str_replace("\n", '<br/>', $text); // convert newlines to HTML breaks } if ($plainMetaInfo === '' || $plainMetaInfo === true) { $plainMetaInfo = $metaInfo; @@ -515,7 +537,7 @@ EOF; $this->ensureBodyListClosed(); $color = $this->themingDefaults->getDefaultColorPrimary(); - $textColor = $this->themingDefaults->getTextColorPrimary(); + $textColor = $this->themingDefaults->getDefaultTextColorPrimary(); $this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]); $this->plainBody .= PHP_EOL . $plainTextLeft . ': ' . $urlLeft . PHP_EOL; @@ -528,7 +550,7 @@ EOF; * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string $url URL of button * @param string|false $plainText Text of button in plain text version - * if empty the $text is used, if false none will be used + * if empty the $text is used, if false none will be used * * @since 12.0.0 */ @@ -546,7 +568,7 @@ EOF; } $color = $this->themingDefaults->getDefaultColorPrimary(); - $textColor = $this->themingDefaults->getTextColorPrimary(); + $textColor = $this->themingDefaults->getDefaultTextColorPrimary(); $this->htmlBody .= vsprintf($this->button, [$color, $color, $url, $color, $textColor, $textColor, $text]); if ($plainText !== false) { diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php index e866cbfdbbc..bdc4d6760e0 100644 --- a/lib/private/Mail/Mailer.php +++ b/lib/private/Mail/Mailer.php @@ -27,6 +27,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Mailer as SymfonyMailer; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\NullTransport; use Symfony\Component\Mailer\Transport\SendmailTransport; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; @@ -52,16 +53,22 @@ 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( - private IConfig $config, - private LoggerInterface $logger, - private Defaults $defaults, - private IURLGenerator $urlGenerator, - private IL10N $l10n, + private IConfig $config, + private LoggerInterface $logger, + private Defaults $defaults, + private IURLGenerator $urlGenerator, + private IL10N $l10n, private IEventDispatcher $dispatcher, - private IFactory $l10nFactory, + private IFactory $l10nFactory, ) { } @@ -97,6 +104,31 @@ class Mailer implements IMailer { * @since 12.0.0 */ public function createEMailTemplate(string $emailId, array $data = []): IEMailTemplate { + $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; + } + $class = $this->config->getSystemValueString('mail_template_class', ''); if ($class !== '' && class_exists($class) && is_a($class, EMailTemplate::class, true)) { @@ -104,6 +136,8 @@ class Mailer implements IMailer { $this->defaults, $this->urlGenerator, $this->l10nFactory, + $logoWidth, + $logoHeight, $emailId, $data ); @@ -113,6 +147,8 @@ class Mailer implements IMailer { $this->defaults, $this->urlGenerator, $this->l10nFactory, + $logoWidth, + $logoHeight, $emailId, $data ); @@ -196,9 +232,6 @@ class Mailer implements IMailer { } /** - * @deprecated 26.0.0 Implicit validation is done in \OC\Mail\Message::setRecipients - * via \Symfony\Component\Mime\Address::__construct - * * @param string $email Email address to be validated * @return bool True if the mail address is valid, false otherwise */ @@ -220,9 +253,10 @@ class Mailer implements IMailer { return $this->instance; } - $transport = null; - switch ($this->config->getSystemValueString('mail_smtpmode', 'smtp')) { + case 'null': + $transport = new NullTransport(); + break; case 'sendmail': $transport = $this->getSendMailInstance(); break; @@ -232,7 +266,9 @@ class Mailer implements IMailer { break; } - return new SymfonyMailer($transport); + $this->instance = new SymfonyMailer($transport); + + return $this->instance; } /** @@ -299,8 +335,10 @@ class Mailer implements IMailer { break; default: $sendmail = \OCP\Server::get(IBinaryFinder::class)->findBinaryPath('sendmail'); - if ($sendmail === null) { + if ($sendmail === false) { + // fallback (though not sure what good it'll do) $sendmail = '/usr/sbin/sendmail'; + $this->logger->debug('sendmail binary search failed, using fallback ' . $sendmail, ['app' => 'core']); } $binaryPath = $sendmail; break; @@ -311,6 +349,7 @@ class Mailer implements IMailer { default => ' -bs', }; + $this->logger->debug('Using sendmail binary: ' . $binaryPath, ['app' => 'core']); return new SendmailTransport($binaryPath . $binaryParam, null, $this->logger); } } diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php index faeba469e20..523a4836760 100644 --- a/lib/private/Mail/Message.php +++ b/lib/private/Mail/Message.php @@ -316,7 +316,7 @@ class Message implements IMessage { public function getAutoSubmitted(): string { $headers = $this->symfonyEmail->getHeaders(); - return $headers->has(AutoSubmitted::HEADER) ? - $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; + return $headers->has(AutoSubmitted::HEADER) + ? $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; } } diff --git a/lib/private/Mail/Provider/Manager.php b/lib/private/Mail/Provider/Manager.php new file mode 100644 index 00000000000..f162d30b834 --- /dev/null +++ b/lib/private/Mail/Provider/Manager.php @@ -0,0 +1,255 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Mail\Provider; + +use OC\AppFramework\Bootstrap\Coordinator; +use OCP\Mail\Provider\IManager; +use OCP\Mail\Provider\IProvider; +use OCP\Mail\Provider\IService; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Throwable; + +class Manager implements IManager { + + protected ?array $providersCollection = null; + + public function __construct( + private Coordinator $coordinator, + private ContainerInterface $container, + private LoggerInterface $logger, + ) { + } + + /** + * Determine if any mail providers are registered + * + * @since 30.0.0 + * + * @return bool + */ + public function has(): bool { + + // return true if collection has any providers + return !empty($this->providers()); + + } + + /** + * Retrieve a count of how many mail providers are registered + * + * @since 30.0.0 + * + * @return int + */ + public function count(): int { + + // return count of providers in collection + return count($this->providers()); + + } + + /** + * Retrieve which mail providers are registered + * + * @since 30.0.0 + * + * @return array<string,string> collection of provider id and label ['jmap' => 'JMap Connector'] + */ + public function types(): array { + + // construct types collection + $types = []; + // extract id and name from providers collection + foreach ($this->providers() as $entry) { + $types[$entry->id()] = $entry->label(); + } + // return types collection + return $types; + + } + + /** + * Retrieve all registered mail providers + * + * @since 30.0.0 + * + * @return array<string,IProvider> collection of provider id and object ['jmap' => IProviderObject] + */ + public function providers(): array { + + // evaluate if we already have a cached collection of providers and return the collection if we do + if (is_array($this->providersCollection)) { + return $this->providersCollection; + } + // retrieve server registration context + $context = $this->coordinator->getRegistrationContext(); + // evaluate if registration context was returned + if ($context === null) { + return []; + } + // initilize cached collection + $this->providersCollection = []; + // iterate through all registered mail providers + foreach ($context->getMailProviders() as $entry) { + try { + /** @var IProvider $provider */ + // object provider + $provider = $this->container->get($entry->getService()); + // add provider to cache collection + $this->providersCollection[$provider->id()] = $provider; + } catch (Throwable $e) { + $this->logger->error( + 'Could not load mail provider ' . $entry->getService() . ': ' . $e->getMessage(), + ['exception' => $e] + ); + } + } + // return mail provider collection + return $this->providersCollection; + + } + + /** + * Retrieve a provider with a specific id + * + * @since 30.0.0 + * + * @param string $providerId provider id + * + * @return IProvider|null + */ + public function findProviderById(string $providerId): ?IProvider { + + // evaluate if we already have a cached collection of providers + if (!is_array($this->providersCollection)) { + $this->providers(); + } + + if (isset($this->providersCollection[$providerId])) { + return $this->providersCollection[$providerId]; + } + // return null if provider was not found + return null; + + } + + /** + * Retrieve all services for all registered mail providers + * + * @since 30.0.0 + * + * @param string $userId user id + * + * @return array<string,array<string,IService>> collection of provider id, service id and object ['jmap' => ['Service1' => IServiceObject]] + */ + public function services(string $userId): array { + + // initilize collection + $services = []; + // retrieve and iterate through mail providers + foreach ($this->providers() as $entry) { + // retrieve collection of services + $mailServices = $entry->listServices($userId); + // evaluate if mail services collection is not empty and add results to services collection + if (!empty($mailServices)) { + $services[$entry->id()] = $mailServices; + } + } + // return collection + return $services; + + } + + /** + * Retrieve a service with a specific id + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $serviceId service id + * @param string $providerId provider id + * + * @return IService|null returns service object or null if none found + */ + public function findServiceById(string $userId, string $serviceId, ?string $providerId = null): ?IService { + + // evaluate if provider id was specified + if ($providerId !== null) { + // find provider + $provider = $this->findProviderById($providerId); + // evaluate if provider was found + if ($provider instanceof IProvider) { + // find service with specific id + $service = $provider->findServiceById($userId, $serviceId); + // evaluate if mail service was found + if ($service instanceof IService) { + return $service; + } + } + } else { + // retrieve and iterate through mail providers + foreach ($this->providers() as $provider) { + // find service with specific id + $service = $provider->findServiceById($userId, $serviceId); + // evaluate if mail service was found + if ($service instanceof IService) { + return $service; + } + } + } + + // return null if no match was found + return null; + + } + + /** + * Retrieve a service for a specific mail address + * returns first service with specific primary address + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $address mail address (e.g. test@example.com) + * @param string $providerId provider id + * + * @return IService|null returns service object or null if none found + */ + public function findServiceByAddress(string $userId, string $address, ?string $providerId = null): ?IService { + + // evaluate if provider id was specified + if ($providerId !== null) { + // find provider + $provider = $this->findProviderById($providerId); + // evaluate if provider was found + if ($provider instanceof IProvider) { + // find service with specific mail address + $service = $provider->findServiceByAddress($userId, $address); + // evaluate if mail service was found + if ($service instanceof IService) { + return $service; + } + } + } else { + // retrieve and iterate through mail providers + foreach ($this->providers() as $provider) { + // find service with specific mail address + $service = $provider->findServiceByAddress($userId, $address); + // evaluate if mail service was found + if ($service instanceof IService) { + return $service; + } + } + } + // return null if no match was found + return null; + + } +} |