use wcf\system\background\BackgroundQueueHandler;
use wcf\system\email\mime\AbstractMimePart;
use wcf\system\email\mime\IRecipientAwareMimePart;
-use wcf\system\email\mime\TextMimePart;
use wcf\system\event\EventHandler;
-use wcf\system\exception\SystemException;
use wcf\util\DateUtil;
use wcf\util\StringUtil;
protected $extraHeaders = [];
/**
- * Text parts of this email
- * @var array
- */
- protected $text = [];
-
- /**
- * Attachments of this email
- * @var array
+ * The body of this Email.
+ * @var AbstractMimePart
*/
- protected $attachments = [];
-
- /**
- * Boundary between the 'Text' parts of this email
- * @var string
- */
- private $textBoundary;
-
- /**
- * Boundary between the mime parts of this email
- * @var string
- */
- private $mimeBoundary;
+ protected $content = null;
/**
* Mail host for use in the Message-Id
*/
private static $host = null;
- /**
- * Generates boundaries for the mime parts.
- */
- public function __construct() {
- $this->textBoundary = "WoltLab_Community_Framework=_".StringUtil::getRandomID();
- $this->mimeBoundary = "WoltLab_Community_Framework=_".StringUtil::getRandomID();
- }
-
/**
* Returns the mail host for use in the Message-Id.
*
throw new \DomainException("The given type '".$type."' is invalid. Must be one of 'to', 'cc', 'bcc'.");
}
- $this->recipients[$recipient->getAddress()] = [$type, $recipient];
+ $this->recipients[$recipient->getAddress()] = [
+ 'type' => $type,
+ 'mailbox' => $recipient
+ ];
}
/**
}
/**
- * Adds a mime part to this email. Should be either \wcf\system\email\mime\TextMimePart
- * or \wcf\system\email\mime\AttachmentMimePart.
- * The given priority determines the ordering within the Email. A higher priority
- * mime part will be further down the email (see RFC 2046, 5.1.4).
- *
- * @param AbstractMimePart $part
- * @param integer $priority
- * @throws \InvalidArgumentException
- * @throws \DomainException
- */
- public function addMimePart(AbstractMimePart $part, $priority = 1000) {
- foreach ($part->getAdditionalHeaders() as $header) {
- $header[0] = mb_strtolower($header[0]);
- if ($header[0] == 'content-type' || $header[0] == 'content-transfer-encoding') {
- throw new \InvalidArgumentException("The header '".$header[0]."' may not be set. Use the proper methods.");
- }
-
- if (!StringUtil::startsWith($header[0], 'x-') && !StringUtil::startsWith($header[0], 'content-')) {
- throw new \DomainException("The header '".$header[0]."' may not be set. You may only set headers starting with 'X-' or 'Content-'.");
- }
- }
-
- switch ($part->getContentTransferEncoding()) {
- case 'base64':
- case 'quoted-printable':
- break;
- default:
- throw new \DomainException("The Content-Transfer-Encoding '".$part->getContentTransferEncoding()."' may not be set. You may only use 'quoted-printable' or 'base64'.");
- }
-
- if ($part instanceof TextMimePart) {
- $this->text[] = [$priority, $part];
- }
- else {
- $this->attachments[] = [$priority, $part];
- }
- }
-
- /**
- * Returns the text mime parts of this email.
- *
- * @return array
- */
- public function getText() {
- return $this->text;
- }
-
- /**
- * Returns the attachments (i.e. the mime parts that are not a TextMimePart) of this email.
+ * Sets the body of this email.
*
- * @return array
+ * @param AbstractMimePart $body
*/
- public function getAttachments() {
- return $this->attachments;
+ public function setBody(AbstractMimePart $body) {
+ $this->body = $body;
}
/**
$to = [];
$cc = [];
foreach ($this->getRecipients() as $recipient) {
- if ($recipient[0] == 'to') $to[] = $recipient[1];
- else if ($recipient[0] == 'cc') $cc[] = $recipient[1];
+ if ($recipient['type'] == 'to') $to[] = $recipient['mailbox'];
+ else if ($recipient['type'] == 'cc') $cc[] = $recipient['mailbox'];
}
$headers[] = ['from', (string) $this->getSender()];
if ($this->getReplyTo()->getAddress() !== $this->getSender()->getAddress()) {
}
$headers[] = ['mime-version', '1.0'];
- if (!$this->text) {
- throw new \LogicException("Cannot generate message headers, you must specify at least one 'Text' part.");
- }
- if ($this->attachments) {
- $headers[] = ['content-type', "multipart/mixed;\r\n boundary=\"".$this->mimeBoundary."\""];
+ if (!$this->body) {
+ throw new \LogicException("Cannot generate message headers, you must set a body.");
}
- else {
- if (count($this->text) > 1) {
- $headers[] = ['content-type', "multipart/alternative;\r\n boundary=\"".$this->textBoundary."\""];
- }
- else {
- $headers[] = ['content-type', $this->text[0][1]->getContentType()];
- $headers[] = ['content-transfer-encoding', $this->text[0][1]->getContentTransferEncoding()];
- $headers = array_merge($headers, $this->text[0][1]->getAdditionalHeaders());
- }
+ $headers[] = ['content-type', $this->body->getContentType()];
+ if ($this->body->getContentTransferEncoding()) {
+ $headers[] = ['content-transfer-encoding', $this->body->getContentTransferEncoding()];
}
+ $headers = array_merge($headers, $this->body->getAdditionalHeaders());
return array_merge($headers, $this->extraHeaders);
}
* @return string
*/
public function getBodyString() {
- $text = "";
- $body = "";
-
- if (count($this->text) > 1 || $this->attachments) {
- $body .= StringUtil::wordwrap("This is a MIME encoded email. As you are seeing this your user agent does not support these.");
- $body .= "\r\n\r\n";
- }
-
- usort($this->text, function ($a, $b) {
- return $a[0] - $b[0];
- });
- foreach ($this->text as $part) {
- if (count($this->text) > 1) {
- $text .= "--".$this->textBoundary."\r\n";
- }
- if (count($this->text) > 1 || $this->attachments) {
- $text .= "content-type: ".$part[1]->getContentType()."\r\n";
- $text .= "content-transfer-encoding: ".$part[1]->getContentTransferEncoding()."\r\n";
- if ($part[1]->getAdditionalHeaders()) {
- $text .= implode("\r\n", array_map(function ($item) {
- return implode(': ', $item);
- }, $part[1]->getAdditionalHeaders()))."\r\n";
- }
- $text .= "\r\n";
- }
- switch ($part[1]->getContentTransferEncoding()) {
- case 'quoted-printable':
- $text .= quoted_printable_encode($part[1]->getContent());
- break;
- case 'base64':
- $text .= chunk_split(base64_encode($part[1]->getContent()));
- break;
- }
- $text .= "\r\n";
- }
- if (count($this->text) > 1) {
- $text .= "--".$this->textBoundary."--\r\n";
- }
-
- if ($this->attachments) {
- $body .= "--".$this->mimeBoundary."\r\n";
- if (count($this->text) > 1) {
- $body .= "Content-Type: multipart/alternative;\r\n boundary=\"".$this->textBoundary."\"\r\n";
- $body .= "\r\n";
- }
- $body .= $text;
-
- foreach ($this->attachments as $part) {
- $body .= "\r\n--".$this->mimeBoundary."\r\n";
- $body .= "content-type: ".$part[1]->getContentType()."\r\n";
- $body .= "content-transfer-encoding: ".$part[1]->getContentTransferEncoding()."\r\n";
- if ($part[1]->getAdditionalHeaders()) {
- $body .= implode("\r\n", array_map(function ($item) {
- return implode(': ', $item);
- }, $part[1]->getAdditionalHeaders()))."\r\n";
- }
- $body .= "\r\n";
- switch ($part[1]->getContentTransferEncoding()) {
- case 'quoted-printable':
- $body .= quoted_printable_encode($part[1]->getContent());
- break;
- case 'base64':
- $body .= chunk_split(base64_encode($part[1]->getContent()));
- break;
- }
- $body .= "\r\n";
- }
- $body .= "--".$this->mimeBoundary."--\r\n";
- }
- else {
- $body .= $text;
+ switch ($this->body->getContentTransferEncoding()) {
+ case 'quoted-printable':
+ return quoted_printable_encode($this->body->getContent());
+ break;
+ case 'base64':
+ return chunk_split(base64_encode($this->body->getContent()));
+ break;
+ case '':
+ return $this->body->getContent();
}
- return $body;
+ throw new \LogicException('Unreachable');
}
/**
foreach ($this->recipients as $recipient) {
$mail = clone $this;
- if ($recipient[1] instanceof UserMailbox) {
- $mail->addHeader('X-Community-Framework-Recipient', $recipient[1]->getUser()->username);
+ if ($recipient['mailbox'] instanceof UserMailbox) {
+ $mail->addHeader('X-Community-Framework-Recipient', $recipient['mailbox']->getUser()->username);
}
- foreach (array_merge($mail->getText(), $mail->getAttachments()) as $mimePart) {
- if ($mimePart[1] instanceof IRecipientAwareMimePart) $mimePart[1]->setRecipient($recipient[1]);
- }
+ if ($this->body instanceof IRecipientAwareMimePart) $this->body->setRecipient($recipient['mailbox']);
- $data = ['mail' => $mail, 'recipient' => $recipient, 'skip' => false];
+ $data = [
+ 'mail' => $mail,
+ 'recipient' => $recipient,
+ 'skip' => false
+ ];
EventHandler::getInstance()->fireAction($this, 'getJobs', $data);
// an event decided that this email should be skipped
if ($data['skip']) continue;
- $jobs[] = new EmailDeliveryBackgroundJob($mail, $recipient[1]);
+ $jobs[] = new EmailDeliveryBackgroundJob($mail, $recipient['mailbox']);
}
return $jobs;
BackgroundQueueHandler::getInstance()->forceCheck();
}
+ /**
+ * @see Email::getEmail()
+ */
+ public function __toString() {
+ return $this->getEmail();
+ }
+
/**
* Returns the email RFC 2822 representation of this email.
*
* @return string
*/
- public function __toString() {
+ public function getEmail() {
return $this->getHeaderString()."\r\n\r\n".$this->getBodyString();
}
}
--- /dev/null
+<?php
+namespace wcf\system\email\mime;
+use wcf\system\email\Mailbox;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a multipart/* mime container.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.email.mime
+ * @category Community Framework
+ * @since 2.2
+ */
+abstract class AbstractMultipartMimePart extends AbstractMimePart implements IRecipientAwareMimePart {
+ /**
+ * The boundary between the distinct parts.
+ * @var string
+ */
+ protected $boundary;
+
+ /**
+ * The parts.
+ * @var \SplObjectStorage
+ */
+ protected $parts;
+
+ /**
+ * Sets the multipart boundary.
+ */
+ public function __construct() {
+ $this->boundary = "WoltLab_Community_Framework=_".StringUtil::getRandomID();
+ $this->parts = new \SplObjectStorage();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getContentTransferEncoding() {
+ return '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setRecipient(Mailbox $mailbox = null) {
+ foreach ($this->parts as $part) {
+ if ($part instanceof IRecipientAwareMimePart) {
+ $part->setRecipient($mailbox);
+ }
+ }
+ }
+
+ /**
+ * Concatenates the given mime parts.
+ *
+ * @param \Traversable $parts
+ */
+ protected function getConcatenatedParts($parts) {
+ $content = "";
+ foreach ($parts as $part) {
+ $content .= "--".$this->boundary."\r\n";
+ $content .= "content-type: ".$part->getContentType()."\r\n";
+ if ($part->getContentTransferEncoding()) {
+ $content .= "content-transfer-encoding: ".$part->getContentTransferEncoding()."\r\n";
+ }
+
+ if ($part->getAdditionalHeaders()) {
+ $content .= implode("\r\n", array_map(function ($item) {
+ return implode(': ', $item);
+ }, $part->getAdditionalHeaders()))."\r\n";
+ }
+ $content .= "\r\n";
+ switch ($part->getContentTransferEncoding()) {
+ case 'quoted-printable':
+ $content .= quoted_printable_encode($part->getContent());
+ break;
+ case 'base64':
+ $content .= chunk_split(base64_encode($part->getContent()));
+ break;
+ case '':
+ $content .= $part->getContent();
+ break;
+ default:
+ throw new \LogicException('Unreachable');
+ }
+ $content .= "\r\n";
+ }
+ $content .= "--".$this->boundary."--\r\n";
+
+ return $content;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getContent() {
+ $content = "";
+ $content .= StringUtil::wordwrap("This is a MIME encoded email. As you are seeing this your user agent does not support these.");
+ $content .= "\r\n\r\n";
+
+ $content .= $this->getConcatenatedParts($this->parts);
+
+ return $content;
+ }
+
+ /**
+ * Adds a mime part to this email. Should be either \wcf\system\email\mime\TextMimePart
+ * or \wcf\system\email\mime\AttachmentMimePart.
+ *
+ * @param AbstractMimePart $part
+ * @param mixed $data Additional data, to be defined by child classes
+ * @throws \InvalidArgumentException
+ * @throws \DomainException
+ */
+ public function addMimePart(AbstractMimePart $part, $data = null) {
+ foreach ($part->getAdditionalHeaders() as $header) {
+ $header[0] = mb_strtolower($header[0]);
+ if ($header[0] == 'content-type' || $header[0] == 'content-transfer-encoding') {
+ throw new \InvalidArgumentException("The header '".$header[0]."' may not be set. Use the proper methods.");
+ }
+
+ if (!StringUtil::startsWith($header[0], 'x-') && !StringUtil::startsWith($header[0], 'content-')) {
+ throw new \DomainException("The header '".$header[0]."' may not be set. You may only set headers starting with 'X-' or 'Content-'.");
+ }
+ }
+
+ switch ($part->getContentTransferEncoding()) {
+ case 'base64':
+ case 'quoted-printable':
+ case '':
+ break;
+ default:
+ throw new \DomainException("The Content-Transfer-Encoding '".$part->getContentTransferEncoding()."' may not be set. You may only use 'quoted-printable' or 'base64' or ''.");
+ }
+
+ $this->parts[$part] = $data;
+ }
+
+ /**
+ * Removes a mime part from this multipart part.
+ *
+ * @param AbstractMimePart $part
+ */
+ public function removeMimePart(AbstractMimePart $part) {
+ $this->parts->detach($part);
+ }
+
+ /**
+ * Returns the stored mime parts of this multipart part.
+ * Note: The returned \SplObjectStorage is a clone of the internal one.
+ * Modifications will not reflect on this object.
+ *
+ * @return \SplObjectStorage
+ */
+ public function getMimeParts() {
+ return clone $this->parts;
+ }
+}