aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Mail/Message.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Mail/Message.php')
-rw-r--r--lib/private/Mail/Message.php266
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;
+ }
}