<?php
namespace wcf\system\mail;
use wcf\data\language\Language;
+use wcf\system\email\mime\AttachmentMimePart;
+use wcf\system\email\mime\MimePartFacade;
+use wcf\system\email\mime\PlainTextMimePart;
+use wcf\system\email\Email;
+use wcf\system\email\Mailbox;
use wcf\system\WCF;
use wcf\util\FileUtil;
*/
public static $lineEnding = "\n";
- /**
- * mail header
- * @var string
- */
- protected $header = '';
-
- /**
- * boundary for multipart/mixed mail
- * @var string
- */
- protected $boundary = '';
-
- /**
- * mail content mime type
- * @var string
- */
- protected $contentType = "text/plain";
-
- /**
- * mail recipients
- * @var string[]
- */
- protected $to = [];
-
- /**
- * mail subject
- * @var string
- */
- protected $subject = '';
-
/**
* mail message
* @var string
*/
protected $message = '';
- /**
- * mail sender
- * @var string
- */
- protected $from = '';
-
- /**
- * mail carbon copy
- * @var string[]
- */
- protected $cc = [];
-
- /**
- * mail blind carbon copy
- * @var string[]
- */
- protected $bcc = [];
-
/**
* mail attachments
* @var array
*/
protected $attachments = [];
- /**
- * priority of the mail
- * @var integer
- */
- protected $priority = 3;
-
- /**
- * mail body
- * @var string
- */
- protected $body = '';
-
/**
* mail language
* @var Language
*/
protected $language = null;
+ /**
+ * the underlying email object
+ * @var Email
+ */
+ protected $email = null;
+
/**
* Creates a new Mail object.
*
* @param string $header
*/
public function __construct($to = '', $subject = '', $message = '', $from = '', $cc = '', $bcc = '', $attachments = [], $priority = '', $header = '') {
- $this->setBoundary();
+ $this->email = new Email();
if (empty($from)) $from = [MAIL_FROM_NAME => MAIL_FROM_ADDRESS];
if (empty($priority)) $priority = 3;
$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);
}
/**
- * 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());
}
/**
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();
}
/**
}
/**
- * 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.');
}
/**
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.');
}
/**
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.');
}
/**
* @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.');
}
/**
+++ /dev/null
-<?php
-namespace wcf\system\mail;
-use wcf\system\exception\SystemException;
-use wcf\system\io\RemoteFile;
-use wcf\util\StringUtil;
-
-/**
- * Sends a Mail with a connection to a smtp server.
- *
- * @author Tim Duesterhus, Alexander Ebert
- * @copyright 2001-2018 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @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);
- }
-}