continue implementing mail

This commit is contained in:
Dennis Eichhorn 2020-04-11 18:21:02 +02:00
parent dc02763fd3
commit 1ec7e62756
3 changed files with 392 additions and 21 deletions

View File

@ -218,7 +218,9 @@ abstract class EmailAbstract
{
$this->submitType = $submitType;
if ($this->submitType === SubmitType::SMTP || $this->submitType === SubmitType::MAIL) {
if ($this->submitType === SubmitType::SMTP
|| $this->submitType === SubmitType::MAIL
) {
$this->endOfLine = $this->submitType === SubmitType::SMTP || !\stripos(\PHP_OS, 'WIN') === 0 ? \PHP_EOL : "\r\n";
return;

View File

@ -16,6 +16,7 @@ namespace phpOMS\Message\Mail;
use phpOMS\System\CharsetType;
use phpOMS\System\MimeType;
use phpOMS\Utils\StringUtils;
/**
* Mail class.
@ -123,6 +124,14 @@ class Mail
*/
protected string $bodyMime = '';
/**
* Mail body.
*
* @var string
* @since 1.0.0
*/
protected string $icalBody = '';
/**
* Mail header.
*
@ -142,10 +151,10 @@ class Mail
/**
* Encoding.
*
* @var int
* @var string
* @since 1.0.0
*/
protected int $encoding = EncodingType::E_8BIT;
protected string $encoding = EncodingType::E_8BIT;
/**
* Mail content type.
@ -155,14 +164,6 @@ class Mail
*/
protected string $contentType = MimeType::M_TXT;
/**
* Boundaries
*
* @var array
* @since 1.0.0
*/
protected array $boundary = [];
/**
* Mail message type.
*
@ -581,15 +582,176 @@ class Mail
$this->id = empty($this->id) ? $this->generatedId() : $this->id;
$output = '';
$this->boundary[0] = 'b0_' . $this->id;
$this->boundary[1] = 'b1_' . $this->id;
$this->boundary[2] = 'b2_' . $this->id;
$this->boundary[3] = 'b3_' . $this->id;
$boundary = [];
$boundary[0] = 'b0_' . $this->id;
$boundary[1] = 'b1_' . $this->id;
$boundary[2] = 'b2_' . $this->id;
$boundary[3] = 'b3_' . $this->id;
$output .= !empty($this->signKeyFile) ? $this->generateMimeHeader() . $this->endOfLine : '';
$output .= !empty($this->signKeyFile) ? $this->generateMimeHeader($boundary) . $this->endOfLine : '';
$body = $this->wrapText($this->body, $this->wordWrap, false);
$bodyEncoding = $this->encoding;
$bodyCharset = $this->charset;
if ($bodyEncoding === EncodingType::E_8BIT && !((bool) preg_match('/[\x80-\xFF]/', $body))) {
$bodyEncoding = EncodingType::E_7BIT;
$bodyCharset = CharsetType::ASCII;
}
if ($this->encoding !== EncodingType::E_BASE64 && ((bool) preg_match('/^(.{' . (63 + strlen($this->endOfLine)) . ',})/m', $body))) {
$bodyEncoding = EncodingType::E_QUOTED;
}
$bodyAlt = $this->wrapText($this->bodyAlt, $this->wordWrap, false);
$bodyAltEncoding = $this->encoding;
$bodyAltCharset = $this->charset;
if ($bodyAlt !== '') {
if ($bodyAltEncoding === EncodingType::E_8BIT && !((bool) preg_match('/[\x80-\xFF]/', $bodyAlt))) {
$bodyAltEncoding = EncodingType::E_7BIT;
$bodyAltCharset = CharsetType::ASCII;
}
if ($this->encoding !== EncodingType::E_BASE64 && ((bool) preg_match('/^(.{' . (63 + strlen($this->endOfLine)) . ',})/m', $bodyAlt))) {
$bodyAltEncoding = EncodingType::E_QUOTED;
}
}
$mimeBody = 'This is a multi-part message in MIME format.' . $this->endOfLine . $this->endOfLine;
switch ($this->messageType) {
case DispositionType::INLINE:
case DispositionType::ATTACHMENT:
$body .= $mimeBody;
$body .= $this->getBoundary($boundary[0], $bodyCharset, $this->contentType, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
$body .= $this->attachAll($this->messageType, $boundary[0]);
break;
case DispositionType::INLINE . '_' . DispositionType::ATTACHMENT:
$body .= $mimeBody;
$body .= '--' . $boundary[0] . $this->endOfLine;
$body .= 'Content-Type: ' . MimeType::M_RELATED . ';' . $this->endOfLine;
$body .= ' boundary ="' . $boundary[1] . '";' . $this->endOfLine;
$body .= ' type ="' . MimeType::M_HTML . '";' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[1], $bodyCharset, $this->contentType, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
$body .= $this->attachAll(DispositionType::INLINE, $boundary[1]);
$body .= $this->endOfLine;
$body .= $this->attachAll(DispositionType::ATTACHMENT, $boundary[1]);
break;
case DispositionType::ALT:
$body .= $mimeBody;
$body .= $this->getBoundary($boundary[0], $bodyAltCharset, MimeType::M_TEXT, $bodyAltEncoding);
$body .= $this->encodeString($this->bodyAlt, $bodyAltEncoding);
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[0], $bodyCharset, MimeType::M_HTML, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
if (!empty($this->icalBody)) {
$method = ICALMethodType::REQUEST;
$constants = ICALMethodType::getConstants();
foreach ($constants as $enum) {
if (\stripos($this->icalBody, 'METHOD:' . $enum) !== false
|| \stripos($this->icalBody, 'METHOD: ' . $enum) !== false
) {
$method = $enum;
break;
}
}
$body .= $this->getBoundary($boundary[0], $this->charset, MimeType::M_ICS . '; method=' . $method, $this->encoding);
$body .= $this->encodeString($this->icalBody, $this->encoding);
$body .= $this->endOfLine;
}
$body .= $this->endOfLine . '--' . $boundary[0] . '--' . $this->endOfLine;
break;
case DispositionType::ALT . '_' . DispositionType::INLINE:
$body .= $mimeBody;
$body .= $this->getBoundary($boundary[0], $bodyAltCharset, MimeType::M_TEXT, $bodyAltEncoding);
$body .= $this->encodeString($this->bodyAlt, $bodyAltEncoding);
$body .= $this->endOfLine;
$body .= '--' . $boundary[0] . $this->endOfLine;
$body .= 'Content-Type: ' . MimeType::M_RELATED . ';' . $this->endOfLine;
$body .= ' boundary="' . $boundary[1] . '";' . $this->endOfLine;
$body .= ' type="' . MimeType::M_HTML . '";' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[1], $bodyCharset, MimeType::M_HTML, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
$body .= $this->attachAll(DispositionType::INLINE, $boundary[1]);
$body .= $this->endOfLine;
$body .= $this->endOfLine . '--' . $boundary[0] . '--' . $this->endOfLine;
break;
case DispositionType::ALT . '_' . DispositionType::ATTACHMENT:
$body .= $mimeBody;
$body .= '--' . $boundary[0] . $this->endOfLine;
$body .= 'Content-Type: ' . MimeType::M_ALT . ';' . $this->endOfLine;
$body .= ' boundary="' . $boundary[1] . '"' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[1], $bodyAltCharset, MimeType::M_TEXT, $bodyAltEncoding);
$body .= $this->encodeString($this->bodyAlt, $bodyAltEncoding);
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[1], $bodyCharset, MimeType::M_HTML, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
if (!empty($this->icalBody)) {
$method = ICALMethodType::REQUEST;
$constants = ICALMethodType::getConstants();
foreach ($constants as $enum) {
if (\stripos($this->icalBody, 'METHOD:' . $enum) !== false
|| \stripos($this->icalBody, 'METHOD: ' . $enum) !== false
) {
$method = $enum;
break;
}
}
$body .= $this->getBoundary($boundary[1], $this->charset, MimeType::M_ICS . '; method=' . $method, $this->encoding);
$body .= $this->encodeString($this->icalBody, $this->encoding);
}
$body .= $this->endOfLine . '--' . $boundary[1] . '--' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->attachAll(DispositionType::ATTACHMENT, $boundary[0]);
break;
case DispositionType::ALT . '_' . DispositionType::INLINE . '_' . DispositionType::ATTACHMENT:
$body .= $mimeBody;
$body .= '--' . $boundary[0] . $this->endOfLine;
$body .= 'Content-Type: ' . MimeType::M_ALT . $this->endOfLine;
$body .= ' boundary="' . $boundary[1] . '"' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[1], $bodyAltCharset, MimeType::M_TEXT, $bodyAltEncoding);
$body .= $this->encodeString($this->bodyAlt, $bodyAltEncoding);
$body .= $this->endOfLine;
$body .= '--' . $boundary[1] . $this->endOfLine;
$body .= 'Content-Type: ' . MimeType::M_RELATED . ';' . $this->endOfLine;
$body .= ' boundary="' . $boundary[2] . '"' . $this->endOfLine;
$body .= ' type="' . MimeType::M_HTML . '"' . $this->endOfLine;
$body .= $this->endOfLine;
$body .= $this->getBoundary($boundary[2], $bodyCharset, MimeType::M_HTML, $bodyEncoding);
$body .= $this->encodeString($this->body, $bodyEncoding);
$body .= $this->endOfLine;
$body .= $this->attachAll(DispositionType::INLINE, $boundary[2]);
$body .= $this->endOfLine;
$body .= $this->endOfLine . '--' . $boundary[2] . '--' . $this->endOfLine;
$body .= $this->attachAll(DispositionType::ATTACHMENT, $boundary[1]);
break;
default:
$body .= $this->encodeString($this->body, $bodyEncoding);
}
if ($this->signKeyFile !== '') {
// @todo implement
}
return $output;
}
@ -630,11 +792,13 @@ class Mail
/**
* Generate the mime header
*
* @param array $boundary Message boundary
*
* @return string
*
* @since 1.0.0
*/
private function generateMimeHeader() : string
private function generateMimeHeader(array $boundary) : string
{
$mime = '';
$isMultipart = true;
@ -642,19 +806,19 @@ class Mail
switch ($this->messageType) {
case DispositionType::INLINE:
$mime .= 'Content-Type:' . MimeType::M_RELATED . ';' . $this->endOfLine;
$mime .= ' boundary="' . $this->boundary[0] . '"' . $this->endOfLine;
$mime .= ' boundary="' . $boundary[0] . '"' . $this->endOfLine;
break;
case DispositionType::ATTACHMENT:
case DispositionType::INLINE . '_' . DispositionType::ATTACHMENT:
case DispositionType::ALT . '_' . DispositionType::ATTACHMENT:
case DispositionType::ALT . '_' . DispositionType::INLINE . '_' . DispositionType::ATTACHMENT:
$mime .= 'Content-Type:' . MimeType::M_MIXED . ';' . $this->endOfLine;
$mime .= ' boundary="' . $this->boundary[0] . '"' . $this->endOfLine;
$mime .= ' boundary="' . $boundary[0] . '"' . $this->endOfLine;
break;
case DispositionType::ALT:
case DispositionType::ALT . '_' . DispositionType::INLINE:
$mime .= 'Content-Type:' . MimeType::M_ALT . ';' . $this->endOfLine;
$mime .= ' boundary="' . $this->boundary[0] . '"' . $this->endOfLine;
$mime .= ' boundary="' . $boundary[0] . '"' . $this->endOfLine;
break;
default:
$mime .= 'Content-Type:' . $this->contentType . '; charset=' . CharsetType::UTF_8 . ';' . $this->endOfLine;
@ -667,9 +831,20 @@ class Mail
: $mime;
}
/**
* Wrap text
*
* @param string $text Text to wrap
* @param int $length Line length
* @param bool $quoted Is quoted
*
* @return string
*
* @since 1.0.0
*/
private function wrapText(string $text, int $length, bool $quoted = false) : string
{
if ($length < 1) {
if ($length < 1 || $text === '') {
return $text;
}
@ -682,12 +857,63 @@ class Mail
$buffer = '';
$output = '';
$crlfLength = \strlen($this->endOfLine);
$first = true;
$isUTF8 = $this->charset === CharsetType::UTF_8;
foreach ($lines as $line) {
$words = \explode(' ', $line);
foreach ($words as $word) {
if ($quoted && \strlen($word) > $length) {
$spaces = $length - \strlen($buffer) - $crlfLength;
if ($first) {
if ($spaces > 20) {
$len = $spaces;
if ($isUTF8) {
$len = StringUtils::utf8CharBoundary($word, $len);
} elseif ('=' === \substr($word, $len - 1, 1)) {
--$len;
} elseif ('=' === \substr($word, $len - 2, 1)) {
$len -= 2;
}
$part = \substr($word, 0, $len);
$word = \substr($word, $len);
$buffer .= ' ' . $part;
$output .= $buffer . '=' . $this->endOfLine;
} else {
$output .= $buffer . $softEndOfLine;
}
$buffer = '';
}
while ($word !== '') {
if ($length < 1) {
break;
}
$len = $length;
if ($isUTF8) {
$len = StringUtils::utf8CharBoundary($word, $len);
} elseif ('=' === \substr($word, $len - 1, 1)) {
--$len;
} elseif ('=' === \substr($word, $len - 2, 1)) {
$len -= 2;
}
$part = \substr($word, 0, $len);
$word = \substr($word, $len);
if ($word !== '') {
$output .= $part . '=' . $this->endOfLine;
} else {
$buffer = $part;
}
}
} else {
$oldBuf = $buffer;
$buffer .= $word . ' ';
@ -704,4 +930,88 @@ class Mail
return $output;
}
/**
* Render the boundary
*
* @param string $boundary Boundary identifier
* @param string $charset Charset
* @param string $contentType ContentType
* @param string $encoding Encoding
*
* @return string
*
* @since 1.0.0
*/
private function getBoundary(string $boundary, string $charset = null, string $contentType = null, string $encoding = null) : string
{
$boundary = '';
$charset = empty($charset) ? $this->charset : $charset;
$contentType = empty($contentType) ? $this->contentType : $contentType;
$encoding = empty($encoding) ? $this->encoding : $encoding;
$boundary .= '--' . $boundary . $this->endOfLine;
$boundary .= 'Content-Type: ' . $contentType . '; charset=' . $charset . $this->endOfLine;
if ($encoding !== EncodingType::E_7BIT) {
$boundary .= 'Content-Transfer-Encoding: ' . $encoding . $this->endOfLine;
}
return $boundary . $this->endOfLine;
}
/**
* Encode a string
*
* @param string $text Text to encode
* @param string $encoding Encoding to use
*
* @return string
*
* @since 1.0.0
*/
private function encodeString(string $text, string $encoding = EncodingType::E_BASE64) : string
{
$encoded = '';
if ($encoding === EncodingType::E_BASE64) {
$encoded = \chunk_split(\base64_encode($text), 76, $this->endOfLine);
} elseif ($encoding === EncodingType::E_7BIT || $encoding === EncodingType::E_8BIT) {
$encoded = $this->normalizeText($text, $this->endOfLine);
if (\substr($encoded, -\strlen($this->endOfLine)) !== $this->endOfLine) {
$encoded .= $this->endOfLine;
}
} elseif ($encoding === EncodingType::E_BINARY) {
$encoded = $text;
} elseif ($encoded === EncodingType::E_QUOTED) {
$encoded = $this->encodeQuoted($text);
}
return $encoded;
}
private function attachAll(string $disposition, string $boundary) : string
{
$mime = [];
$cid = [];
$incl = [];
foreach ($this->attachment as $attach) {
}
$mime[] = '--' . $boundary . '--' . $this->endOfLine;
return \implode('', $mime);
}
private function encodeQuoted()
{
}
private function encodeHeader()
{
}
}

View File

@ -15,6 +15,7 @@ declare(strict_types=1);
namespace phpOMS\Utils;
use phpOMS\Contract\RenderableInterface;
use phpOMS\System\CharsetType;
/**
* String utils class.
@ -588,4 +589,62 @@ final class StringUtils
return ['values' => $diffValues, 'mask' => $diffMask];
}
/**
* Get the utf-8 boundary of a string
*
* @param string $text To search for utf-8 boundary
* @param int $offset Search offset
*
* @return int
*
* @since 1.0.0
*/
public static function utf8CharBoundary(string $text, int $offset = 0) : int
{
$reset = 3;
$pos = $offset;
do {
$lastChunk = \substr($text, $pos - $reset, $reset);
$encodedPos = \strpos($lastChunk, '=');
if ($encodedPos === false) {
break;
}
$hex = \substr($text, $pos - $reset + $encodedPos + 1, 2);
$dec = \hexdec($hex);
if ($dec < 128) {
if ($encodedPos > 0) {
$pos -= $reset - $encodedPos;
}
break;
} elseif ($dec >= 192) {
$pos -= $reset - $encodedPos;
break;
} elseif ($dec < 192) {
$reset +=3;
}
} while(true);
return $pos;
}
/**
* Test if a string has multibytes
*
* @param string $text Text to check
* @param string $charset Charset to check
*
* @return bool
*
* @since 1.0.0
*/
public static function hasMultiBytes(string $text, string $charset = CharsetType::UTF_8) : bool
{
return \strlen($text) > \mb_strlen($text, $charset);
}
}