From 4fb6319f03fe22334e6e16ce15bf736ff87c0b67 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 23 Oct 2018 19:59:32 +0200 Subject: [PATCH] Map \wcf\system\mail\Mail to \wcf\system\email\Email see #2754 --- .../lib/system/mail/DebugMailSender.class.php | 50 --- .../files/lib/system/mail/Mail.class.php | 324 +++++------------- .../lib/system/mail/MailSender.class.php | 51 --- .../lib/system/mail/PHPMailSender.class.php | 21 -- .../lib/system/mail/SMTPMailSender.class.php | 298 ---------------- 5 files changed, 85 insertions(+), 659 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/mail/DebugMailSender.class.php delete mode 100644 wcfsetup/install/files/lib/system/mail/MailSender.class.php delete mode 100644 wcfsetup/install/files/lib/system/mail/PHPMailSender.class.php delete mode 100644 wcfsetup/install/files/lib/system/mail/SMTPMailSender.class.php diff --git a/wcfsetup/install/files/lib/system/mail/DebugMailSender.class.php b/wcfsetup/install/files/lib/system/mail/DebugMailSender.class.php deleted file mode 100644 index e2cf92371b..0000000000 --- a/wcfsetup/install/files/lib/system/mail/DebugMailSender.class.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @package WoltLabSuite\Core\Data\Mail - * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*. - */ -class DebugMailSender extends MailSender { - /** - * log file - * @var File - */ - protected $log = null; - - /** - * Writes the given e-mail in a log file. - * - * @param Mail $mail - */ - public function sendMail(Mail $mail) { - if ($this->log === null) { - $logFilePath = WCF_DIR . 'log/'; - $this->log = new File($logFilePath . 'mail.log', 'ab'); - } - - $this->log->write(self::printMail($mail)); - } - - /** - * Prints the given mail. - * - * @param Mail $mail - * @return string - */ - protected static function printMail(Mail $mail) { - return "Date: ".gmdate('r')."\n". - "To: ".$mail->getToString()."\n". - "Subject: ".$mail->getSubject()."\n". - $mail->getHeader()."\n". - "Attachments: ".print_r($mail->getAttachments(), true)."\n\n". - $mail->getMessage()."\n\n"; - } -} diff --git a/wcfsetup/install/files/lib/system/mail/Mail.class.php b/wcfsetup/install/files/lib/system/mail/Mail.class.php index c4f07f725c..5a2600132f 100644 --- a/wcfsetup/install/files/lib/system/mail/Mail.class.php +++ b/wcfsetup/install/files/lib/system/mail/Mail.class.php @@ -1,6 +1,11 @@ setBoundary(); + $this->email = new Email(); if (empty($from)) $from = [MAIL_FROM_NAME => MAIL_FROM_ADDRESS]; if (empty($priority)) $priority = 3; @@ -121,7 +72,7 @@ class Mail { $this->setSubject($subject); $this->setMessage($message); $this->setPriority($priority); - $this->setHeader($header); + if ($header != '') $this->setHeader($header); if (!empty($to)) $this->addTo($to); if (!empty($cc)) $this->addCC($cc); @@ -131,122 +82,65 @@ class Mail { } /** - * Creates and returns a basic header for the email. + * Returns the underlying email object * - * @return string mail header + * @return Email */ - public function getHeader() { - if (!empty($this->header)) { - $this->header = preg_replace('%(\r\n|\r|\n)%', self::$lineEnding, $this->header); - } + public function getEmail() { + $attachments = array_map(function ($attachment) { + return new AttachmentMimePart($attachment['path'], $attachment['name']); + }, $this->attachments); - $this->header .= - 'X-Priority: 3'.self::$lineEnding - .'X-Mailer: WoltLab Suite Mail Package'.self::$lineEnding - .'From: '.$this->getFrom().self::$lineEnding - .($this->getCCString() != '' ? 'CC:'.$this->getCCString().self::$lineEnding : '') - .($this->getBCCString() != '' ? 'BCC:'.$this->getBCCString().self::$lineEnding : ''); - - if (count($this->getAttachments())) { - $this->header .= 'Content-Transfer-Encoding: 8bit'.self::$lineEnding; - $this->header .= 'Content-Type: multipart/mixed;'.self::$lineEnding; - $this->header .= "\tboundary=".'"'.$this->getBoundary().'";'.self::$lineEnding; - } - else { - $this->header .= 'Content-Transfer-Encoding: 8bit'.self::$lineEnding; - $this->header .= 'Content-Type: '.$this->getContentType().'; charset=UTF-8'.self::$lineEnding; - } + $text = new PlainTextMimePart($this->message); - $this->header .= 'MIME-Version: 1.0'.self::$lineEnding; + $this->email->setBody(new MimePartFacade([$text], $attachments)); - return $this->header; + return $this->email; } /** - * Creates and returns the recipients list (TO, CC, BCC). - * - * @param boolean $withTo - * @return string + * @throws \BadMethodCallException always + */ + public function getHeader() { + throw new \BadMethodCallException('This method is unavailable.'); + } + + /** + * @throws \BadMethodCallException always */ public function getRecipients($withTo = false) { - $recipients = ''; - if ($withTo && $this->getToString() != '') $recipients .= 'TO:'.$this->getToString().self::$lineEnding; - if ($this->getCCString() != '') $recipients .= 'CC:'.$this->getCCString().self::$lineEnding; - if ($this->getBCCString() != '') $recipients .= 'BCC:'.$this->getBCCString().self::$lineEnding; - return $recipients; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Creates and returned the body (Message, Attachments) for the email. - * - * @return string mail body + * @throws \BadMethodCallException always */ public function getBody() { - $counter = 1; - $this->body = ''; - - if (count($this->getAttachments())) { - // add message - $this->body .= '--'.$this->getBoundary().self::$lineEnding; - $this->body .= 'Content-Type: '.$this->getContentType().'; charset="UTF-8"'.self::$lineEnding; - $this->body .= 'Content-Transfer-Encoding: 8bit'.self::$lineEnding; - $this->body .= self::$lineEnding; - - // wrap lines after 70 characters - $this->body .= wordwrap($this->getMessage(), 70); - $this->body .= self::$lineEnding.self::$lineEnding; - $this->body .= '--'.$this->getBoundary().self::$lineEnding; - - // add attachments - foreach ($this->getAttachments() as $attachment) { - $fileName = $attachment['name']; - $path = $attachment['path']; - - // download file - if (FileUtil::isURL($path)) { - $tmpPath = FileUtil::getTemporaryFilename('mailAttachment_'); - if (!@copy($path, $tmpPath)) continue; - $path = $tmpPath; - } - - // get file contents - $data = @file_get_contents($path); - $data = chunk_split(base64_encode($data), 70, self::$lineEnding); - - $this->body .= 'Content-Type: application/octetstream; name="'.$fileName.'"'.self::$lineEnding; - $this->body .= 'Content-Transfer-Encoding: base64'.self::$lineEnding; - $this->body .= 'Content-Disposition: attachment; filename="'.$fileName.'"'.self::$lineEnding.self::$lineEnding; - $this->body .= $data.self::$lineEnding.self::$lineEnding; - - if ($counter < count($this->getAttachments())) $this->body .= '--'.$this->getBoundary().self::$lineEnding; - $counter++; - } - - $this->body .= self::$lineEnding.'--'.$this->getBoundary().'--'; - } - else { - $this->body .= $this->getMessage(); - } - return $this->body; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Builds a formatted address: "$name" <$email>. - * - * @param string $name - * @param string $email - * @param boolean $encodeName - * @return string + * @throws \BadMethodCallException always */ public static function buildAddress($name, $email, $encodeName = true) { - return $email; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Sends this mail. + * @see Email::send() */ public function send() { - MailSender::getInstance()->sendMail($this); + $this->getEmail()->send(); + } + + protected function makeMailbox($email, $name = null) { + if ($name) { + return new Mailbox($email, $name, $this->getLanguage()); + } + if (preg_match('~^(.+) <([^>]+)>$~', $email, $matches)) { + return new Mailbox($matches[2], $matches[1], $this->getLanguage()); + } + return new Mailbox($email, null, $this->getLanguage()); } /** @@ -257,48 +151,40 @@ class Mail { public function addTo($to) { if (is_array($to)) { foreach ($to as $name => $recipient) { - $this->to[] = self::buildAddress($name, $recipient); + $this->email->addRecipient($this->makeMailbox($recipient, $name)); } } else { - $this->to[] = $to; + $this->email->addRecipient($this->makeMailbox($to)); } } /** - * Returns the recipients of this mail. - * - * @return mixed + * @throws \BadMethodCallException always */ public function getTo() { - return $this->to; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Returns the list of recipients. - * - * @return string + * @throws \BadMethodCallException always */ public function getToString() { - return implode(', ', $this->to); + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Sets the subject of this mail. - * - * @param string $subject + * @see Email::setSubject() */ public function setSubject($subject) { - $this->subject = $subject; + $this->email->setSubject($subject); } /** - * Returns the subject of this mail. - * - * @return string + * @see Email::getSubject() */ public function getSubject() { - return $this->subject; + return $this->email->getSubject(); } /** @@ -311,35 +197,29 @@ class Mail { } /** - * Returns the message of this mail. - * - * @return string + * @throws \BadMethodCallException always */ public function getMessage() { - return preg_replace('%(\r\n|\r|\n)%', self::$lineEnding, $this->message . (MAIL_SIGNATURE ? self::$lineEnding . self::$lineEnding . '-- ' . self::$lineEnding . $this->getLanguage()->get(MAIL_SIGNATURE) : '')); + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Sets the sender of this mail. - * - * @param mixed $from + * @see Email::setSender() */ public function setFrom($from) { if (is_array($from)) { - $this->from = self::buildAddress(key($from), current($from)); + $this->email->setSender($this->makeMailbox(current($from), key($from))); } else { - $this->from = $from; + $this->email->setSender($this->makeMailbox($from)); } } /** - * Returns the sender of this mail. - * - * @return string + * @throws \BadMethodCallException always */ public function getFrom() { - return $this->from; + throw new \BadMethodCallException('This method is unavailable.'); } /** @@ -350,30 +230,26 @@ class Mail { public function addCC($cc) { if (is_array($cc)) { foreach ($cc as $name => $recipient) { - $this->cc[] = self::buildAddress($name, $recipient); + $this->email->addRecipient($this->makeMailbox($recipient, $name), 'cc'); } } else { - $this->cc[] = $cc; + $this->email->addRecipient($this->makeMailbox($cc), 'cc'); } } /** - * Returns the carbon copy recipients of this mail. - * - * @return mixed + * @throws \BadMethodCallException always */ public function getCC() { - return $this->cc; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Returns the carbon copy recipients of this mail as string. - * - * @return string + * @throws \BadMethodCallException always */ public function getCCString() { - return implode(', ', $this->cc); + throw new \BadMethodCallException('This method is unavailable.'); } /** @@ -384,30 +260,26 @@ class Mail { public function addBCC($bcc) { if (is_array($bcc)) { foreach ($bcc as $name => $recipient) { - $this->bcc[] = self::buildAddress($name, $recipient); + $this->email->addRecipient($this->makeMailbox($recipient, $name), 'bcc'); } } else { - $this->bcc[] = $bcc; + $this->email->addRecipient($this->makeMailbox($bcc), 'bcc'); } } /** - * Returns the blind carbon copy recipients of this mail. - * - * @return mixed + * @throws \BadMethodCallException always */ public function getBCC() { - return $this->bcc; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Returns the blind carbon copy recipients of this mail as string. - * - * @return string + * @throws \BadMethodCallException always */ public function getBCCString() { - return implode(', ', $this->bcc); + throw new \BadMethodCallException('This method is unavailable.'); } /** @@ -444,61 +316,35 @@ class Mail { * @param integer $priority */ public function setPriority($priority) { - $this->priority = $priority; + $this->email->addHeader('x-priority', $priority); } /** - * Returns the priority of the mail - * - * @return integer + * @throws \BadMethodCallException always */ public function getPriority() { - return $this->priority; - } - - /** - * Creates a boundary for multipart/mixed mail. - */ - protected function setBoundary() { - $this->boundary = "==Multipart_Boundary_x".bin2hex(\random_bytes(20))."x"; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Returns the created boundary. - * - * @return string - */ - protected function getBoundary() { - return $this->boundary; - } - - /** - * Returns the content type. - * - * @return string + * @throws \BadMethodCallException always */ public function getContentType() { - return $this->contentType; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Sets the content type. - * - * @param string $contentType + * @throws \BadMethodCallException always */ public function setContentType($contentType) { - $this->contentType = $contentType; + throw new \BadMethodCallException('This method is unavailable.'); } /** - * Sets an additional header. - * - * @param string $header + * @throws \BadMethodCallException always */ public function setHeader($header) { - if (!empty($header)) { - $this->header .= $header.self::$lineEnding; - } + throw new \BadMethodCallException('This method is unavailable.'); } /** diff --git a/wcfsetup/install/files/lib/system/mail/MailSender.class.php b/wcfsetup/install/files/lib/system/mail/MailSender.class.php deleted file mode 100644 index 9a8fa5dac7..0000000000 --- a/wcfsetup/install/files/lib/system/mail/MailSender.class.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @package WoltLabSuite\Core\Data\Mail - * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*. - */ -abstract class MailSender { - /** - * unique mail server instance - * @var MailSender - */ - protected static $instance = null; - - /** - * Returns the default mail sender. - * - * @return MailSender - */ - public static function getInstance() { - if (self::$instance === null) { - switch (MAIL_SEND_METHOD) { - case 'php': - self::$instance = new PHPMailSender(); - break; - - case 'smtp': - self::$instance = new SMTPMailSender(); - break; - - case 'debug': - self::$instance = new DebugMailSender(); - break; - } - } - - return self::$instance; - } - - /** - * Sends an e-mail. - * - * @param Mail $mail - */ - abstract public function sendMail(Mail $mail); -} diff --git a/wcfsetup/install/files/lib/system/mail/PHPMailSender.class.php b/wcfsetup/install/files/lib/system/mail/PHPMailSender.class.php deleted file mode 100644 index 77597b3c3f..0000000000 --- a/wcfsetup/install/files/lib/system/mail/PHPMailSender.class.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @package WoltLabSuite\Core\Data\Mail - * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*. - */ -class PHPMailSender extends MailSender { - /** - * @inheritDoc - */ - public function sendMail(Mail $mail) { - if (MAIL_USE_F_PARAM) return @mb_send_mail($mail->getToString(), $mail->getSubject(), $mail->getBody(), $mail->getHeader(), '-f'.MAIL_FROM_ADDRESS); - else return @mb_send_mail($mail->getToString(), $mail->getSubject(), $mail->getBody(), $mail->getHeader()); - } -} diff --git a/wcfsetup/install/files/lib/system/mail/SMTPMailSender.class.php b/wcfsetup/install/files/lib/system/mail/SMTPMailSender.class.php deleted file mode 100644 index e87b15bb7f..0000000000 --- a/wcfsetup/install/files/lib/system/mail/SMTPMailSender.class.php +++ /dev/null @@ -1,298 +0,0 @@ - - * @package WoltLabSuite\Core\Data\Mail - * @deprecated The Community Framework 2.x mail API is deprecated in favor of \wcf\system\email\*. - */ -class SMTPMailSender extends MailSender { - /** - * smtp connection - * @var RemoteFile - */ - protected $connection = null; - - /** - * last received status code - * @var string - */ - protected $statusCode = ''; - - /** - * last received status message - * @var string - */ - protected $statusMsg = ''; - - /** - * mail recipients - * @var array - */ - protected $recipients = []; - - /** - * Creates a new SMTPMailSender object. - */ - public function __construct() { - Mail::$lineEnding = "\r\n"; - } - - /** - * Destroys the SMTPMailSender object. - */ - public function __destruct() { - $this->disconnect(); - } - - /** - * Connects to the smtp-server - */ - protected function connect() { - // connect - $this->connection = new RemoteFile(MAIL_SMTP_HOST, MAIL_SMTP_PORT); - $this->getSMTPStatus(); - if ($this->statusCode != 220) { - throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'")); - } - - $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''; - if (empty($host)) { - $host = gethostname(); - if ($host === false) { - $host = 'localhost'; - } - } - - // send ehlo - $this->write('EHLO '.$host); - $extensions = explode(Mail::$lineEnding, $this->read()); - $this->getSMTPStatus(array_shift($extensions)); - if ($this->statusCode == 250) { - $extensions = array_map(function($element) { - return strtolower(substr($element, 4)); - }, $extensions); - - if ($this->connection->hasTLSSupport() && in_array('starttls', $extensions)) { - $this->write('STARTTLS'); - $this->getSMTPStatus(); - - if ($this->statusCode != 220) { - throw new SystemException($this->formatError("cannot enable STARTTLS, though '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."' advertised it")); - } - - if (!$this->connection->setTLS(true)) { - throw new SystemException('enabling TLS failed'); - } - - // repeat EHLO - $this->write('EHLO '.$host); - $extensions = explode(Mail::$lineEnding, $this->read()); - $this->getSMTPStatus(array_shift($extensions)); - - if ($this->statusCode != 250) { - throw new SystemException($this->formatError("could not EHLO after enabling STARTTLS at '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'")); - } - } - - // do authentication - if (MAIL_SMTP_USER != '' || MAIL_SMTP_PASSWORD != '') { - $this->auth(); - } - } - else { - // send helo - $this->write('HELO '.$host); - $this->getSMTPStatus(); - if ($this->statusCode != 250) { - throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'")); - } - } - } - - /** - * Formats a smtp error message. - * - * @param string $message - * @return string - */ - protected function formatError($message) { - return $message.': '.$this->statusMsg.' ('.$this->statusCode.')'; - } - - /** - * Does the authentification of the client on the server - */ - protected function auth() { - // init authentication - $this->write('AUTH LOGIN'); - $this->getSMTPStatus(); - - // checks if auth is supported - if ($this->statusCode != 334) { - throw new SystemException($this->formatError("smtp mail server '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."' does not support user authentication")); - } - - // sending user information to smtp-server - $this->write(base64_encode(MAIL_SMTP_USER)); - $this->getSMTPStatus(); - if ($this->statusCode != 334) { - throw new SystemException($this->formatError("unknown smtp user '".MAIL_SMTP_USER."'")); - } - - $this->write(base64_encode(MAIL_SMTP_PASSWORD)); - $this->getSMTPStatus(); - if ($this->statusCode != 235) { - throw new SystemException($this->formatError("invalid password for smtp user '".MAIL_SMTP_USER."'")); - } - } - - /** - * @inheritDoc - */ - public function sendMail(Mail $mail) { - $this->recipients = []; - if (count($mail->getTo()) > 0) $this->recipients = $mail->getTo(); - if (count($mail->getCC()) > 0) $this->recipients = array_merge($this->recipients, $mail->getCC()); - if (count($mail->getBCC())> 0) $this->recipients = array_merge($this->recipients, $mail->getBCC()); - - // apply connection - if ($this->connection === null) { - $this->connect(); - } - - // send mail - $this->write('MAIL FROM:<'.$mail->getFrom().'>'); - $this->getSMTPStatus(); - if ($this->statusCode != 250) { - $this->abort(); - throw new SystemException($this->formatError("wrong from format '".$mail->getFrom()."'")); - } - - // recipients - $recipientCounter = 0; - foreach ($this->recipients as $recipient) { - $this->write('RCPT TO:<'.$recipient.'>'); - $this->getSMTPStatus(); - if ($this->statusCode != 250 && $this->statusCode != 251) { - if ($this->statusCode < 550) { - $this->abort(); - throw new SystemException($this->formatError("wrong recipient format '".$recipient."'")); - } - continue; - } - $recipientCounter++; - } - if (!$recipientCounter) { - $this->abort(); - return; - } - - // data - $this->write("DATA"); - $this->getSMTPStatus(); - if ($this->statusCode != 354 && $this->statusCode != 250) { - $this->abort(); - throw new SystemException($this->formatError("smtp error")); - } - - $serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; - if (empty($serverName)) { - $serverName = gethostname(); - if ($serverName === false) { - $serverName = 'localhost'; - } - } - - $header = - "Date: ".gmdate('r').Mail::$lineEnding - ."To: ".$mail->getToString().Mail::$lineEnding - ."Message-ID: <".md5(uniqid())."@".$serverName.">".Mail::$lineEnding - ."Subject: ".Mail::encodeMIMEHeader($mail->getSubject()).Mail::$lineEnding - .$mail->getHeader(); - - $this->write($header); - $this->write(""); - $lines = explode(Mail::$lineEnding, $mail->getBody()); - foreach ($lines as $line) { - // 4.5.2 Transparency - // o Before sending a line of mail text, the SMTP client checks the - // first character of the line. If it is a period, one additional - // period is inserted at the beginning of the line. - if (StringUtil::startsWith($line, '.')) $line = '.'.$line; - $this->write($line); - } - $this->write("."); - - $this->getSMTPStatus(); - if ($this->statusCode != 250) { - $this->abort(); - throw new SystemException($this->formatError("message sending failed")); - } - } - - /** - * Disconnects the Client-Server connection - */ - public function disconnect() { - if ($this->connection === null) { - return; - } - - $this->write("QUIT"); - $this->read(); - $this->connection->close(); - $this->connection = null; - } - - /** - * Reads the Information which the Server sends back. - * - * @return string - */ - protected function read() { - $result = ''; - while ($read = $this->connection->gets()) { - $result .= $read; - if (substr($read, 3, 1) == " ") break; - } - - return $result; - } - - /** - * Aborts the current process. This is needed in case a new mail should be - * sent after a exception has occured - */ - protected function abort() { - $this->write("RSET"); - $this->read(); // read response, but do not care about status here - } - - /** - * Extracts error code and message from a server message. - * - * @param string $data - */ - protected function getSMTPStatus($data = null) { - if ($data === null) $data = $this->read(); - $this->statusCode = intval(substr($data, 0, 3)); - $this->statusMsg = substr($data, 4); - } - - /** - * Sends Information to the smtp-Server - * - * @param string $data - */ - protected function write($data) { - $this->connection->puts($data.Mail::$lineEnding); - } -} -- 2.20.1