mirror of
https://github.com/processwire/processwire.git
synced 2025-08-26 16:14:35 +02:00
Some reworking of and improvements to the WireMail class, though no change to external interface (and should be no changes required for classes extending it).
This commit is contained in:
@@ -51,6 +51,7 @@
|
|||||||
* #pw-body
|
* #pw-body
|
||||||
*
|
*
|
||||||
* @method int send() Send email.
|
* @method int send() Send email.
|
||||||
|
* @method string htmlToText($html) Convert HTML email body to TEXT email body.
|
||||||
*
|
*
|
||||||
* @property array $to To email address.
|
* @property array $to To email address.
|
||||||
* @property array $toName Optional person’s name to accompany “to” email address
|
* @property array $toName Optional person’s name to accompany “to” email address
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
* @property array $headers Alias of $header
|
* @property array $headers Alias of $header
|
||||||
* @property array $param Associative array of aditional params (likely not applicable to most WireMail modules).
|
* @property array $param Associative array of aditional params (likely not applicable to most WireMail modules).
|
||||||
* @property array $attachments Array of file attachments (if populated and where supported) #pw-advanced
|
* @property array $attachments Array of file attachments (if populated and where supported) #pw-advanced
|
||||||
|
* @property string $newline Newline character, populated only if different from CRLF. #pw-advanced
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -487,6 +489,25 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the multipart boundary string for this email
|
||||||
|
*
|
||||||
|
* @param string|bool $prefix Specify optional boundary prefix or boolean true to clear any existing stored boundary
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function multipartBoundary($prefix = '') {
|
||||||
|
$boundary = parent::get('_multipartBoundary');
|
||||||
|
if(empty($boundary) || $prefix === true) {
|
||||||
|
$boundary = "==Multipart_Boundary_x" . md5(time()) . "x";
|
||||||
|
parent::set('_multipartBoundary', $boundary);
|
||||||
|
}
|
||||||
|
if(is_string($prefix) && !empty($prefix)) {
|
||||||
|
$boundary = str_replace("_Boundary_x", "_Boundary_{$prefix}_x", $boundary);
|
||||||
|
}
|
||||||
|
return $boundary;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the email
|
* Send the email
|
||||||
*
|
*
|
||||||
@@ -499,87 +520,32 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
*/
|
*/
|
||||||
public function ___send() {
|
public function ___send() {
|
||||||
|
|
||||||
$from = $this->from;
|
// prep header and body
|
||||||
if(!strlen($from)) $from = $this->wire('config')->adminEmail;
|
$this->multipartBoundary(true);
|
||||||
if(!strlen($from)) $from = 'processwire@' . $this->wire('config')->httpHost;
|
$header = $this->renderMailHeader();
|
||||||
|
$body = $this->renderMailBody();
|
||||||
|
|
||||||
$header = "From: " . ($this->fromName ? $this->bundleEmailAndName($from, $this->fromName) : $from);
|
// adjust for the cases where people want to change RFC standard \r\n to just \n
|
||||||
|
$newline = parent::get('newline');
|
||||||
foreach($this->header as $key => $value) $header .= "\r\n$key: $value";
|
if(is_string($newline) && strlen($newline) && $newline !== "\r\n") {
|
||||||
|
$body = str_replace("\r\n", $newline, $body);
|
||||||
$param = $this->wire('config')->phpMailAdditionalParameters;
|
$header = str_replace("\r\n", $newline, $header);
|
||||||
if(is_null($param)) $param = '';
|
|
||||||
foreach($this->param as $value) $param .= " $value";
|
|
||||||
|
|
||||||
$header = trim($header);
|
|
||||||
$param = trim($param);
|
|
||||||
$text = $this->body;
|
|
||||||
$html = $this->bodyHTML;
|
|
||||||
|
|
||||||
if($this->bodyHTML || count($this->attachments)) {
|
|
||||||
if(!strlen($text)) $text = strip_tags($html);
|
|
||||||
$contentType = count($this->attachments) ? 'multipart/mixed' : 'multipart/alternative';
|
|
||||||
$boundary = "==Multipart_Boundary_x" . md5(time()) . "x";
|
|
||||||
$header .= "\r\nMIME-Version: 1.0";
|
|
||||||
$header .= "\r\nContent-Type: $contentType;\r\n boundary=\"$boundary\"";
|
|
||||||
|
|
||||||
// Plain Text
|
|
||||||
$body = "This is a multi-part message in MIME format.\r\n\r\n" .
|
|
||||||
"--$boundary\r\n";
|
|
||||||
|
|
||||||
$textbody = "Content-Type: text/plain; charset=\"utf-8\"\r\n" .
|
|
||||||
"Content-Transfer-Encoding: quoted-printable\r\n\r\n" .
|
|
||||||
quoted_printable_encode($text) . "\r\n\r\n";
|
|
||||||
|
|
||||||
// HTML
|
|
||||||
if($this->bodyHTML){
|
|
||||||
$htmlbody = "Content-Type: text/html; charset=\"utf-8\"\r\n" .
|
|
||||||
"Content-Transfer-Encoding: quoted-printable\r\n\r\n" .
|
|
||||||
quoted_printable_encode($html) . "\r\n\r\n";
|
|
||||||
|
|
||||||
if(count($this->attachments)) {
|
|
||||||
$subboundary = "==Multipart_Boundary_alt_x" . md5(time()) . "x";
|
|
||||||
|
|
||||||
$body .= "Content-Type: multipart/alternative;\r\n boundary=\"$subboundary\"\r\n\r\n" .
|
|
||||||
"--$subboundary\r\n" .
|
|
||||||
$textbody .
|
|
||||||
"--$subboundary\r\n" .
|
|
||||||
$htmlbody .
|
|
||||||
"--$subboundary--\r\n\r\n";
|
|
||||||
} else {
|
|
||||||
$body .= $textbody .
|
|
||||||
"--$boundary\r\n" .
|
|
||||||
$htmlbody;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$body .= $textbody;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attachments
|
|
||||||
foreach($this->attachments as $filename => $file) {
|
|
||||||
$content = file_get_contents($file);
|
|
||||||
$content = chunk_split(base64_encode($content));
|
|
||||||
|
|
||||||
$body .= "--$boundary\r\n" .
|
|
||||||
"Content-Type: application/octet-stream; name=\"$filename\"\r\n" .
|
|
||||||
"Content-Transfer-Encoding: base64\r\n" .
|
|
||||||
"Content-Disposition: attachment; filename=\"$filename\"\r\n\r\n" .
|
|
||||||
"$content\r\n\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$body .= "--$boundary--\r\n";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$header .= "\r\nContent-Type: text/plain; charset=UTF-8\r\n" .
|
|
||||||
"Content-Transfer-Encoding: quoted-printable";
|
|
||||||
$body = quoted_printable_encode($text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prep any additional PHP mail params
|
||||||
|
$param = $this->wire('config')->phpMailAdditionalParameters;
|
||||||
|
if(is_null($param)) $param = '';
|
||||||
|
foreach($this->param as $value) {
|
||||||
|
$param .= " $value";
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email(s)
|
||||||
$numSent = 0;
|
$numSent = 0;
|
||||||
|
$subject = $this->encodeSubject($this->subject);
|
||||||
|
|
||||||
foreach($this->to as $to) {
|
foreach($this->to as $to) {
|
||||||
$toName = $this->mail['toName'][$to];
|
$toName = isset($this->mail['toName'][$to]) ? $this->mail['toName'][$to] : '';
|
||||||
if($toName) $to = $this->bundleEmailAndName($to, $toName); // bundle to "User Name <user@example.com"
|
if($toName) $to = $this->bundleEmailAndName($to, $toName); // bundle to "User Name <user@example.com"
|
||||||
$subject = $this->encodeSubject($this->subject);
|
|
||||||
if($param) {
|
if($param) {
|
||||||
if(@mail($to, $subject, $body, $header, $param)) $numSent++;
|
if(@mail($to, $subject, $body, $header, $param)) $numSent++;
|
||||||
} else {
|
} else {
|
||||||
@@ -590,6 +556,192 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
return $numSent;
|
return $numSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render email header string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function renderMailHeader() {
|
||||||
|
|
||||||
|
$from = $this->from;
|
||||||
|
if(!strlen($from)) $from = $this->wire('config')->adminEmail;
|
||||||
|
if(!strlen($from)) $from = 'processwire@' . $this->wire('config')->httpHost;
|
||||||
|
|
||||||
|
$header = "From: " . ($this->fromName ? $this->bundleEmailAndName($from, $this->fromName) : $from);
|
||||||
|
|
||||||
|
foreach($this->header as $key => $value) {
|
||||||
|
$header .= "\r\n$key: $value";
|
||||||
|
}
|
||||||
|
|
||||||
|
$boundary = $this->multipartBoundary();
|
||||||
|
$header = trim($this->strReplace($header, $boundary));
|
||||||
|
|
||||||
|
if($this->bodyHTML || count($this->attachments)) {
|
||||||
|
$contentType = count($this->attachments) ? 'multipart/mixed' : 'multipart/alternative';
|
||||||
|
$header .=
|
||||||
|
"\r\nMIME-Version: 1.0" .
|
||||||
|
"\r\nContent-Type: $contentType;\r\n boundary=\"$boundary\"";
|
||||||
|
} else {
|
||||||
|
$header .=
|
||||||
|
"\r\nContent-Type: text/plain; charset=UTF-8" .
|
||||||
|
"\r\nContent-Transfer-Encoding: quoted-printable";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render mail body
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function renderMailBody() {
|
||||||
|
|
||||||
|
$boundary = $this->multipartBoundary();
|
||||||
|
$subboundary = $this->multipartBoundary('alt');
|
||||||
|
|
||||||
|
// don’t allow boundary to appear in visible portions of email
|
||||||
|
$text = $this->strReplace($this->body, array($boundary, $subboundary));
|
||||||
|
$html = $this->strReplace($this->bodyHTML, array($boundary, $subboundary));
|
||||||
|
|
||||||
|
// if plain text only, return now
|
||||||
|
if(empty($html) && !count($this->attachments)) return quoted_printable_encode($text);
|
||||||
|
|
||||||
|
// if only HTML provided, generate text version from HTML
|
||||||
|
if(!strlen($text) && strlen($html)) $text = $this->htmlToText($html);
|
||||||
|
|
||||||
|
$body =
|
||||||
|
"This is a multi-part message in MIME format.\r\n\r\n" .
|
||||||
|
"--$boundary\r\n";
|
||||||
|
|
||||||
|
// Plain Text
|
||||||
|
$textbody =
|
||||||
|
"Content-Type: text/plain; charset=\"utf-8\"\r\n" .
|
||||||
|
"Content-Transfer-Encoding: quoted-printable\r\n\r\n" .
|
||||||
|
quoted_printable_encode($text) . "\r\n\r\n";
|
||||||
|
|
||||||
|
if($this->bodyHTML) {
|
||||||
|
// HTML
|
||||||
|
$htmlbody =
|
||||||
|
"Content-Type: text/html; charset=\"utf-8\"\r\n" .
|
||||||
|
"Content-Transfer-Encoding: quoted-printable\r\n\r\n" .
|
||||||
|
quoted_printable_encode($html) . "\r\n\r\n";
|
||||||
|
|
||||||
|
if(count($this->attachments)) {
|
||||||
|
// file attachments
|
||||||
|
$textbody = $this->strReplace($textbody, $subboundary);
|
||||||
|
$htmlbody = $this->strReplace($htmlbody, $subboundary);
|
||||||
|
|
||||||
|
$body .=
|
||||||
|
"Content-Type: multipart/alternative;\r\n boundary=\"$subboundary\"\r\n\r\n" .
|
||||||
|
"--$subboundary\r\n" .
|
||||||
|
$textbody .
|
||||||
|
"--$subboundary\r\n" .
|
||||||
|
$htmlbody .
|
||||||
|
"--$subboundary--\r\n\r\n";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no file attachments
|
||||||
|
$body .=
|
||||||
|
$textbody .
|
||||||
|
"--$boundary\r\n" .
|
||||||
|
$htmlbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// plain text
|
||||||
|
$body .= $textbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($this->attachments)) {
|
||||||
|
$body .= $this->renderMailAttachments();
|
||||||
|
}
|
||||||
|
|
||||||
|
$body .= "--$boundary--\r\n";
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render mail attachments string for placement in body
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function renderMailAttachments() {
|
||||||
|
$body = '';
|
||||||
|
$boundary = $this->multipartBoundary();
|
||||||
|
|
||||||
|
foreach($this->attachments as $filename => $file) {
|
||||||
|
|
||||||
|
$filename = $this->wire('sanitizer')->text($filename, array(
|
||||||
|
'maxLength' => 512,
|
||||||
|
'truncateTail' => false,
|
||||||
|
'stripSpace' => '-',
|
||||||
|
'stripQuotes' => true
|
||||||
|
));
|
||||||
|
|
||||||
|
if(stripos($filename, $boundary) !== false) continue;
|
||||||
|
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
$content = chunk_split(base64_encode($content));
|
||||||
|
|
||||||
|
if(stripos($content, $boundary) !== false) continue;
|
||||||
|
|
||||||
|
$body .=
|
||||||
|
"--$boundary\r\n" .
|
||||||
|
"Content-Type: application/octet-stream; name=\"$filename\"\r\n" .
|
||||||
|
"Content-Transfer-Encoding: base64\r\n" .
|
||||||
|
"Content-Disposition: attachment; filename=\"$filename\"\r\n\r\n" .
|
||||||
|
"$content\r\n\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive string replacement
|
||||||
|
*
|
||||||
|
* This is better than using str_replace() because it handles cases where replacement
|
||||||
|
* results in the construction of a new $find that was not present in original $str.
|
||||||
|
* Note: this function ignores case.
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
* @param string|array $find
|
||||||
|
* @param string $replace
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function strReplace($str, $find, $replace = '') {
|
||||||
|
if(!is_array($find)) $find = array($find);
|
||||||
|
if(!is_string($str)) $str = (string) $str;
|
||||||
|
foreach($find as $findStr) {
|
||||||
|
if(is_array($findStr)) continue;
|
||||||
|
while(stripos($str, $findStr) !== false) {
|
||||||
|
$str = str_ireplace($findStr, $replace, $str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert HTML mail body to TEXT mail body
|
||||||
|
*
|
||||||
|
* @param string $html
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function ___htmlToText($html) {
|
||||||
|
$textTools = new WireTextTools();
|
||||||
|
$this->wire($textTools);
|
||||||
|
$text = $textTools->markupToText($html);
|
||||||
|
$text = str_replace("\n", "\r\n", $text);
|
||||||
|
$text = $this->strReplace($text, $this->multipartBoundary());
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a subject, use mbstring if available
|
* Encode a subject, use mbstring if available
|
||||||
*
|
*
|
||||||
@@ -601,6 +753,9 @@ class WireMail extends WireData implements WireMailInterface {
|
|||||||
*/
|
*/
|
||||||
public function encodeSubject($subject) {
|
public function encodeSubject($subject) {
|
||||||
|
|
||||||
|
$boundary = $this->multipartBoundary();
|
||||||
|
$subject = $this->strReplace($subject, $boundary);
|
||||||
|
|
||||||
if(extension_loaded("mbstring")) {
|
if(extension_loaded("mbstring")) {
|
||||||
// Need to pass in the header name and subtract it afterwards,
|
// Need to pass in the header name and subtract it afterwards,
|
||||||
// otherwise the first line would grow too long
|
// otherwise the first line would grow too long
|
||||||
|
Reference in New Issue
Block a user