diff options
Diffstat (limited to 'lib/private/Mail/Message.php')
-rw-r--r-- | lib/private/Mail/Message.php | 266 |
1 files changed, 144 insertions, 122 deletions
diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php index c1b08b4e231..523a4836760 100644 --- a/lib/private/Mail/Message.php +++ b/lib/private/Mail/Message.php @@ -1,93 +1,87 @@ <?php declare(strict_types=1); - /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arne Hamann <kontakt+github@arne.email> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Jared Boone <jared.boone@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Mail; +use OCP\Mail\Headers\AutoSubmitted; use OCP\Mail\IAttachment; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMessage; -use Swift_Message; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\RfcComplianceException; /** - * Class Message provides a wrapper around SwiftMail + * Class Message provides a wrapper around Symfony\Component\Mime\Email (Used to be around SwiftMail) * * @package OC\Mail */ class Message implements IMessage { - /** @var Swift_Message */ - private $swiftMessage; - /** @var bool */ - private $plainTextOnly; - - public function __construct(Swift_Message $swiftMessage, bool $plainTextOnly) { - $this->swiftMessage = $swiftMessage; - $this->plainTextOnly = $plainTextOnly; + private array $to = []; + private array $from = []; + private array $replyTo = []; + private array $cc = []; + private array $bcc = []; + + public function __construct( + private Email $symfonyEmail, + private bool $plainTextOnly, + ) { } /** - * @param IAttachment $attachment - * @return $this * @since 13.0.0 + * @return $this */ public function attach(IAttachment $attachment): IMessage { /** @var Attachment $attachment */ - $this->swiftMessage->attach($attachment->getSwiftAttachment()); + $attachment->attach($this->symfonyEmail); + return $this; + } + + /** + * Can be used to "attach content inline" as message parts with specific MIME type and encoding. + * {@inheritDoc} + * @since 26.0.0 + */ + public function attachInline(string $body, string $name, ?string $contentType = null): IMessage { + # To be sure this works with iCalendar messages, we encode with 8bit instead of + # quoted-printable encoding. We save the current encoder, replace the current + # encoder with an 8bit encoder and after we've finished, we reset the encoder + # to the previous one. Originally intended to be added after the message body, + # as it is curently unknown if all mail clients handle this properly if added + # before. + $this->symfonyEmail->embed($body, $name, $contentType); return $this; } /** - * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains - * FIXME: Remove this once SwiftMailer supports IDN + * Converts the [['displayName' => 'email'], ['displayName2' => 'email2']] arrays to valid Adresses * - * @param array $addresses Array of mail addresses, key will get converted - * @return array Converted addresses if `idn_to_ascii` exists + * @param array $addresses Array of mail addresses + * @return Address[] + * @throws RfcComplianceException|InvalidArgumentException */ protected function convertAddresses(array $addresses): array { - if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46')) { - return $addresses; - } - $convertedAddresses = []; - foreach ($addresses as $email => $readableName) { - if (!is_numeric($email)) { - [$name, $domain] = explode('@', $email, 2); - $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - $convertedAddresses[$name.'@'.$domain] = $readableName; + if (empty($addresses)) { + return []; + } + + array_walk($addresses, function ($readableName, $email) use (&$convertedAddresses) { + if (is_numeric($email)) { + $convertedAddresses[] = new Address($readableName); } else { - [$name, $domain] = explode('@', $readableName, 2); - $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - $convertedAddresses[$email] = $name.'@'.$domain; + $convertedAddresses[] = new Address($email, $readableName); } - } + }); return $convertedAddresses; } @@ -101,41 +95,31 @@ class Message implements IMessage { * @return $this */ public function setFrom(array $addresses): IMessage { - $addresses = $this->convertAddresses($addresses); - - $this->swiftMessage->setFrom($addresses); + $this->from = $addresses; return $this; } /** * Get the from address of this message. - * - * @return array */ public function getFrom(): array { - return $this->swiftMessage->getFrom() ?? []; + return $this->from; } /** * Set the Reply-To address of this message - * - * @param array $addresses * @return $this */ public function setReplyTo(array $addresses): IMessage { - $addresses = $this->convertAddresses($addresses); - - $this->swiftMessage->setReplyTo($addresses); + $this->replyTo = $addresses; return $this; } /** * Returns the Reply-To address of this message - * - * @return string */ - public function getReplyTo(): string { - return $this->swiftMessage->getReplyTo(); + public function getReplyTo(): array { + return $this->replyTo; } /** @@ -145,19 +129,15 @@ class Message implements IMessage { * @return $this */ public function setTo(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setTo($recipients); + $this->to = $recipients; return $this; } /** * Get the to address of this message. - * - * @return array */ public function getTo(): array { - return $this->swiftMessage->getTo() ?? []; + return $this->to; } /** @@ -167,19 +147,15 @@ class Message implements IMessage { * @return $this */ public function setCc(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setCc($recipients); + $this->cc = $recipients; return $this; } /** * Get the cc address of this message. - * - * @return array */ public function getCc(): array { - return $this->swiftMessage->getCc() ?? []; + return $this->cc; } /** @@ -189,104 +165,114 @@ class Message implements IMessage { * @return $this */ public function setBcc(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setBcc($recipients); + $this->bcc = $recipients; return $this; } /** * Get the Bcc address of this message. - * - * @return array */ public function getBcc(): array { - return $this->swiftMessage->getBcc() ?? []; + return $this->bcc; } /** - * Set the subject of this message. - * - * @param string $subject - * @return IMessage + * @return $this */ public function setSubject(string $subject): IMessage { - $this->swiftMessage->setSubject($subject); + $this->symfonyEmail->subject($subject); return $this; } /** * Get the from subject of this message. - * - * @return string */ public function getSubject(): string { - return $this->swiftMessage->getSubject(); + return $this->symfonyEmail->getSubject() ?? ''; } /** - * Set the plain-text body of this message. - * - * @param string $body * @return $this */ public function setPlainBody(string $body): IMessage { - $this->swiftMessage->setBody($body); + $this->symfonyEmail->text($body); return $this; } /** * Get the plain body of this message. - * - * @return string */ public function getPlainBody(): string { - return $this->swiftMessage->getBody(); + /** @var string $body */ + $body = $this->symfonyEmail->getTextBody() ?? ''; + return $body; } /** - * Set the HTML body of this message. Consider also sending a plain-text body instead of only an HTML one. - * - * @param string $body * @return $this */ - public function setHtmlBody($body) { + public function setHtmlBody(string $body): IMessage { if (!$this->plainTextOnly) { - $this->swiftMessage->addPart($body, 'text/html'); + $this->symfonyEmail->html($body); } return $this; } /** - * Get's the underlying SwiftMessage - * @param Swift_Message $swiftMessage + * Set the underlying Email instance */ - public function setSwiftMessage(Swift_Message $swiftMessage): void { - $this->swiftMessage = $swiftMessage; + public function setSymfonyEmail(Email $symfonyEmail): void { + $this->symfonyEmail = $symfonyEmail; } /** - * Get's the underlying SwiftMessage - * @return Swift_Message + * Get the underlying Email instance */ - public function getSwiftMessage(): Swift_Message { - return $this->swiftMessage; + public function getSymfonyEmail(): Email { + return $this->symfonyEmail; } /** - * @param string $body - * @param string $contentType * @return $this */ - public function setBody($body, $contentType) { + public function setBody(string $body, string $contentType): IMessage { if (!$this->plainTextOnly || $contentType !== 'text/html') { - $this->swiftMessage->setBody($body, $contentType); + if ($contentType === 'text/html') { + $this->symfonyEmail->html($body); + } else { + $this->symfonyEmail->text($body); + } } return $this; } /** - * @param IEMailTemplate $emailTemplate + * Set the recipients on the symphony email + * + * Since + * + * setTo + * setFrom + * setReplyTo + * setCc + * setBcc + * + * could throw a \Symfony\Component\Mime\Exception\RfcComplianceException + * or a \Symfony\Component\Mime\Exception\InvalidArgumentException + * we wrap the calls here. We then have the validation errors all in one place and can + * throw shortly before \OC\Mail\Mailer::send + * + * @throws InvalidArgumentException|RfcComplianceException + */ + public function setRecipients(): void { + $this->symfonyEmail->to(...$this->convertAddresses($this->getTo())); + $this->symfonyEmail->from(...$this->convertAddresses($this->getFrom())); + $this->symfonyEmail->replyTo(...$this->convertAddresses($this->getReplyTo())); + $this->symfonyEmail->cc(...$this->convertAddresses($this->getCc())); + $this->symfonyEmail->bcc(...$this->convertAddresses($this->getBcc())); + } + + /** * @return $this */ public function useTemplate(IEMailTemplate $emailTemplate): IMessage { @@ -297,4 +283,40 @@ class Message implements IMessage { } return $this; } + + /** + * Add the Auto-Submitted header to the email, preventing most automated + * responses to automated messages. + * + * @param AutoSubmitted::VALUE_* $value (one of AutoSubmitted::VALUE_NO, AutoSubmitted::VALUE_AUTO_GENERATED, AutoSubmitted::VALUE_AUTO_REPLIED) + * @return $this + */ + public function setAutoSubmitted(string $value): IMessage { + $headers = $this->symfonyEmail->getHeaders(); + + if ($headers->has(AutoSubmitted::HEADER)) { + // if the header already exsists, remove it. + // the value can be modified with some implementations + // of the interface \Swift_Mime_Header, however the + // interface doesn't, and this makes the static-code + // analysis unhappy. + // @todo check if symfony mailer can modify the autosubmitted header + $headers->remove(AutoSubmitted::HEADER); + } + + $headers->addTextHeader(AutoSubmitted::HEADER, $value); + + return $this; + } + + /** + * Get the current value of the Auto-Submitted header. Defaults to "no" + * which is equivalent to the header not existing at all + */ + public function getAutoSubmitted(): string { + $headers = $this->symfonyEmail->getHeaders(); + + return $headers->has(AutoSubmitted::HEADER) + ? $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; + } } |