rendered paste body<?php
/**
* Email class
*
* @copyright 2002-2007 by papaya Software GmbH - All rights reserved.
* @link http://www.papaya-cms.com/
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU General Public License, version 2
*
* You can redistribute and/or modify this script under the terms of the GNU General Public
* License (GPL) version 2, provided that the copyright and license notes, including these
* lines, remain unmodified. papaya is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE.
*
* @package papaya_base
* @author David Rekowski <rekowski@papaya-cms.com>
* @author Thomas Weinert <weinert@papaya-cms.com>
* @version $Id: sys_email.php 3884 2007-09-13 15:24:51Z weinert $
*/
/**
* base class
*/
require_once(PAPAYA_INCLUDE_PATH.'system/sys_base_object.php');
/**
* string handling class
*/
require_once(PAPAYA_INCLUDE_PATH.'system/papaya_strings.php');
/**
* Email class
* @package papaya_base
* @author David Rekowski <rekowski@papaya-cms.com>
* @author Thomas Weinert <weinert@papaya-cms.com>
*/
class email extends base_object {
/**
* Open tag
* @var string $tagOpen
* @access private
*/
var $tagOpen = '{%';
/**
* Close tag
* @var string $tagClose
* @access private
*/
var $tagClose = '%}';
/**
* array with "To" recipients
*
* (email, name) - list
* @var array
* @access private
*/
var $addressTo = array();
/**
* array with "CC" recipients
*
* (email, name) - list
* @var array
* @access private
*/
var $addressCC = array();
/**
* array with "BCC" recipients
*
* (email, name) - list
* @var array
* @access private
*/
var $addressBCC = array();
/**
* array with sender data
*
* (email, name)
* @var array
* @access private
*/
var $addressFrom = array();
/**
* Headers
* @var array Headers
* @access private
*/
var $headers;
/**
* Subject
* @var string
* @access private
*/
var $subject = '';
/**
* text body
* @var string
*/
var $body = '';
/**
* html body
* @var string
* @access private
*/
var $bodyHTML = '';
/**
* attachements and embedded resources
* @var array
* @access private
*/
var $attachments = array();
/**
* attachements and embedded resources
* @var array
* @access private
*/
var $images = array();
/**
* base charset - should be utf-8 all times
* @var string
* @access private
*/
var $charset = 'utf-8';
/**
* encoding for headers and default encoding
*
* attachments use base64
* @var string
* @access private
*/
var $encoding = 'quoted-printable';
/**
* encoding for plain text part
* @var string
*/
var $encodingText = 'quoted-printable';
/**
* encoding for html part
* @var string
*/
var $encodingHTML = 'quoted-printable';
/**
* line breaks in message
* @var string
* @access private
*/
var $ln = "\n";
/**
* line breaks in message header
* @var string
* @access private
*/
var $headerLn = "\n";
/**
* indent char for header linebreaks
* @var string
*/
var $indentChar = "\t";
/**
* array with used content ids
* @var array
*/
var $contentIds = array();
/**
* Constructor
*
* @param mixed $charset optional, charset for text/html parts
* @param mixed $encoding optional, encoding for text/html parts
* @access public
*/
function __construct($charset = NULL, $encoding = NULL) {
if (isset($charset)) {
$this->charset = $charset;
}
if (isset($encoding)) {
$this->encoding = $encoding;
}
}
/**
* add a recipient
*
* samples:
* addAdress('name@domain.tld'),
* addAdress('Name <name@domain.tld>'),
* addAdress('name@domain.tld', 'Name', 'BCC')
*
* @param $address
* @param string $name optional, default value ''
* @param string $mode optional, default value 'To'
* @access public
*/
function addAddress($address, $name = '', $mode = 'To') {
if ($this->hasNoLineBreaks($address) && $this->hasNoLineBreaks($name)) {
if (preg_match('~^\s*(.*)\s*<([^>]+)>~', $address, $matches)) {
$address = $matches[2];
$name = $matches[1];
}
switch (strtoupper($mode)) {
case 'BCC' :
$this->addressBCC[] = array($address, papaya_strings::ensureUTF8(trim($name)));
break;
case 'CC' :
$this->addressCC[] = array($address, papaya_strings::ensureUTF8(trim($name)));
break;
case 'TO' :
default :
$this->addressTo[] = array($address, papaya_strings::ensureUTF8(trim($name)));
break;
}
}
}
/**
* Set email sender (From, ReplyTo)
*
* @param string $address
* @param string $name optional, default value ''
* @access public
*/
function setSender($address, $name = '') {
if ($this->hasNoLineBreaks($address) && $this->hasNoLineBreaks($name)) {
if (preg_match('~^\s*(.*)\s*<([^>]+)>~', $address, $matches)) {
$address = $matches[2];
$name = $matches[1];
}
$this->addressFrom = array($address, papaya_strings::ensureUTF8(trim($name)));
}
}
/**
* Get current email sender
*
* @access public
* @return string
*/
function getSender() {
if (isset($this->addressFrom) && is_array($this->addressFrom)) {
if (isset($this->addressFrom[0]) && isset($this->addressFrom[1])) {
return $this->formatAddress($this->addressFrom[0], $this->addressFrom[1]);
} elseif (isset($this->addressFrom[0])) {
return $this->formatAddress($this->addressFrom[0]);
}
}
return FALSE;
}
/**
* attach a file
*
* @param string $fileName
* @param string $mimeType optional, default value 'application/octet-stream'
* @param string $encoding optional, default value 'base64'
* @access public
* @return boolean
*/
function addAttachment($fileName, $mimeType = 'application/octet-stream', $encoding = 'base64') {
if (file_exists($fileName) && is_file($fileName) && is_readable($fileName)) {
$fileTitle = basename($fileName);
$this->attachments[] = array(
'file' => $fileName,
'title' => $fileTitle,
'encoding' => $encoding,
'mimetype' => $mimeType,
'cid' => ''
);
return TRUE;
} else {
$this->addMsg(MSG_ERROR, 'Can not read attachment file.');
return FALSE;
}
}
/**
* attach data
*
* @param string $fileTitle filename (attachment.ext)
* @param string $data binary data string
* @param string $mimeType optional, default value 'application/octet-stream'
* @param string $encoding optional, default value 'base64'
* @access public
* @return boolean
*/
function addAttachmentData($fileTitle, $data, $mimeType = 'application/octet-stream', $encoding = 'base64') {
if (!empty($data)) {
$this->attachments[] = array(
'title' => $fileTitle,
'encoding' => $encoding,
'mimetype' => $mimeType,
'cid' => '',
'data' => $data
);
return TRUE;
} else {
$this->addMsg(MSG_ERROR, 'Can not read attachment data.');
return FALSE;
}
}
/**
* embed image file
*
* @param string $fileName
* @param string $cId content id (<img src="cid:..."/>)
* @param string $mimeType optional, default value 'image/jpeg'
* @param string $encoding optional, default value 'base64'
* @access public
* @return boolean
*/
function addImage($fileName, $cId, $mimeType = 'image/jpeg', $encoding = 'base64') {
if (file_exists($fileName) && is_file($fileName) && is_readable($fileName)) {
$fileTitle = basename($fileName);
$this->images[] = array(
'file' => $fileName,
'title' => $fileTitle,
'encoding' => $encoding,
'mimetype' => $mimeType,
'cid' => $cId
);
return TRUE;
} else {
$this->addMsg(MSG_ERROR, 'Can not read image file.');
return FALSE;
}
}
/**
* embed image data
*
* @param string $fileTitle filename (image.ext)
* @param string $data binary data string
* @param string $cId content id (<img src="cid:..."/>)
* @param string $mimeType optional, default value 'image/jpeg'
* @param string $encoding optional, default value 'base64'
* @access public
* @return boolean
*/
function addImageData($fileTitle, $data, $cId, $mimeType = 'image/jpeg', $encoding = 'base64') {
if (!empty($data)) {
$this->images[] = array(
'title' => $fileTitle,
'encoding' => $encoding,
'mimetype' => $mimeType,
'cid' => $cId,
'data' => $data
);
return TRUE;
} else {
$this->addMsg(MSG_ERROR, 'Can not read image data.');
return FALSE;
}
}
/**
* set message subject
*
* @param string $str
* @param array $fillValues replace {%key%} -> value
* @access public
* @return boolean
*/
function setSubject($str, $fillValues = NULL) {
if ($this->hasNoLineBreaks($str)) {
$this->subject = $this->replaceTemplateMarkers(papaya_strings::ensureUTF8(trim($str)), $fillValues);
return TRUE;
}
return FALSE;
}
/**
* set mesaage plain text body
*
* @param string $str
* @param array $fillValues replace {%key%} -> value
* @access public
*/
function setBody($str, $fillValues = NULL, $wrapColumn = 0) {
$str = $this->replaceTemplateMarkers(papaya_strings::ensureUTF8($str), $fillValues);
if ($wrapColumn > 10) {
$lines = explode("\n", $str);
$result = '';
if (isset($lines) && is_array($lines) && count($lines) > 0) {
foreach ($lines as $line) {
$result .= wordwrap(chop($line), $wrapColumn, $this->ln).$this->ln;
}
$str = substr($result, 0, -strlen($this->ln));
}
}
$this->body = $this->reformatLineEnds($str);
}
/**
* set mesaage plain text body
*
* @param string $str
* @param array $fillValues replace {%key%} -> value
* @access public
*/
function setBodyHTML($str, $fillValues = NULL) {
$str = $this->replaceTemplateMarkers(papaya_strings::ensureUTF8($str), $fillValues);
$str = $this->reformatLineEnds($this->replaceTagURIs($str));
$str = htmlentities($str, ENT_NOQUOTES, 'utf-8');
$charTable = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES));
$str = str_replace(array_keys($charTable), array_values($charTable), $str);
$this->bodyHTML = $str;
}
/**
* replace tag uri
*
* image src attributes with cid:...
*
* @param $str
* @access private
* @return string
*/
function replaceTagURIs($str) {
$basePath = $_SERVER['DOCUMENT_ROOT'].dirname($_SERVER['PHP_SELF']).'/';
$patternImg = '~(<img[^>]+src=)(["\'])(.*?)(\\2)([^>]*>)~i';
$patternBackImg = '~(<(body|table|td)[^>]+background=)(["\'])(.*?)(\\3)([^>]*>)~i';
$patternA = '~(<a[^>]+href=)(["\'])(.*?)(\\2)([^>]*>)~i';
$replace = array();
$uris = array();
if (preg_match_all($patternImg, $str, $matches, PREG_SET_ORDER)) {
foreach($matches as $match) {
if (!isset($uris['img'][$match[3]])) {
if ($fileSrc = $this->embedImage($match[3])) {
$replace[$match[0]] = $match[1].'"'.$fileSrc.'"'.$match[5];
$uris['img'][$match[3]] = $fileSrc;
}
} elseif (!isset($replace[$match[0]])) {
$replace[$match[0]] = $match[1].'"'.$uris['img'][$match[3]].'"'.$match[5];
}
}
}
if (preg_match_all($patternBackImg, $str, $matches, PREG_SET_ORDER)) {
foreach($matches as $match) {
if (!isset($uris['img'][$match[4]])) {
if ($fileSrc = $this->embedImage($match[4])) {
$replace[$match[0]] = $match[1].'"'.$fileSrc.'"'.$match[6];
$uris['img'][$match[4]] = $fileSrc;
}
} elseif (!isset($replace[$match[0]])) {
$replace[$match[0]] = $match[1].'"'.$uris['img'][$match[4]].'"'.$match[6];
}
}
}
if (preg_match_all($patternA, $str, $matches, PREG_SET_ORDER)) {
foreach($matches as $match) {
if (!isset($uris['a'][$match[3]])) {
$url = trim($match[3]);
if (preg_match('~^(mailto:|#)~i', $url)) {
$fileSrc = $url;
} else {
$fileSrc = $this->getAbsoluteURL($url, '', FALSE);
}
$replace[$match[0]] = $match[1].'"'.$fileSrc.'"'.$match[5];
$uris['a'][$match[3]] = $fileSrc;
} elseif (!isset($replace[$match[0]])) {
$replace[$match[0]] = $match[1].'"'.$uris['a'][$match[3]].'"'.$match[5];
}
}
}
if (is_array($replace) && count($replace) > 0) {
return str_replace(array_keys($replace), array_values($replace), $str);
}
return $str;
}
/**
* try to embed image and return cid or absolute url
*
* @param $fileSrc
* @access public
* @return string
*/
function embedImage($fileSrc) {
if (0 === strpos($fileSrc, '/')) {
$basePath = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']);
if (substr($basePath, -1) == '/') {
$basePath = substr($basePath, 0, -1);
}
} else {
$basePath = $this->getBasePath(TRUE);
}
$fileSrcInternal = $fileSrc;
if (defined('PAPAYA_ADMIN_PAGE') && PAPAYA_ADMIN_PAGE && substr($fileSrcInternal, 0, 3) == '../') {
$fileSrcInternal = substr($fileSrcInternal, 3);
} elseif (substr($fileSrc, 0, 2) == './') {
$fileSrcInternal = substr($fileSrcInternal, 2);
}
if (defined('PAPAYA_PATH_WEB')) {
$fileSrcInternal = PAPAYA_PATH_WEB.$fileSrcInternal;
} else {
$fileSrcInternal = '/'.$fileSrcInternal;
}
$data = $this->parseRequestURI($fileSrcInternal);
$fileName = NULL;
switch ($data['output']) {
case 'image' :
$pImage = '~/?(sid([a-z]*([\da-f]{32}))/)?(([a-z\d_-]+/)*)([a-z\d_-]+)\.(image)(\.(jpg))(\?.*)?~i';
if (preg_match($pImage, $fileSrc, $regs)) {
if (!(isset($this->imgGenerator) && is_object($this->imgGenerator))) {
include_once(PAPAYA_INCLUDE_PATH.'system/base_imagegenerator.php');
$this->imgGenerator = &new base_imagegenerator();
}
$params = $this->decodeQueryString($regs[10]);
if ($this->imgGenerator->loadByIdent(@$regs[6])) {
$fileName = $this->imgGenerator->generateImage(FALSE, @$params['img']);
}
}
break;
case 'media' :
switch ($data['ext']) {
case 'thumb' :
if (file_exists(PAPAYA_PATH_THUMBFILES.$data['media_id'])) {
$fileName = PAPAYA_PATH_THUMBFILES.$data['media_id'];
break;
}
case 'media' :
$fileName = PAPAYA_PATH_MEDIAFILES.$data['media_id'];
break;
}
break;
default :
$fileName = $basePath.$fileSrc;
break;
}
if (isset($fileName) && is_file($fileName) && is_readable($fileName)) {
list(,,$type) = @getimagesize($fileName);
switch ($type) {
case 3 :
$mimeType = 'image/png';
break;
case 2 :
$mimeType = 'image/jpeg';
break;
case 1 :
$mimeType = 'image/gif';
break;
default :
$mimeType = NULL;
}
if (isset($mimeType)) {
$cId = $this->generateContentId();
$this->addImageData(basename($fileSrc), file_get_contents($fileName), $cId, $mimeType);
return 'cid:'.$cId;
}
}
return $this->getAbsoluteURL($fileSrc, '', FALSE);
}
/**
* Decode query string
*
* @param string $queryString
* @access private
* @return array $queryParams query parameters
*/
function decodeQueryString($queryString) {
$queryParams = array();
if (preg_match_all('~(^|[?&])(([^=&?]+)=([^&]+))|([^&]+)~', $queryString, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if (isset($match[3]) && $match[3] != '') {
$null = NULL;
if (preg_match_all('~([^\[\]]+)(\[|\]|$)~', $match[3], $nameParts)) {
$currentParam = &$queryParams;
$max = count($nameParts[1])-1;
foreach ($nameParts[1] as $idx=>$namePart) {
if ($max == $idx) {
$currentParam[$namePart] = &$match[4];
} else {
$currentParam = &$currentParam[$namePart];
}
}
}
} elseif (isset($match[5])) {
$queryParams[$match[5]] = TRUE;
}
}
}
return $queryParams;
}
/**
* generate a content id
*
* @param string $prefix Prefix string for content id
* @access private
* @return string
*/
function generateContentId($prefix = 'img') {
do {
$cId = $prefix.md5(rand(0, time()));
} while (isset($this->contentIds[$cId]));
$this->contentIds[$cId] = TRUE;
return $cId;
}
function hasBodyText() {
return (isset($this->body) && $this->body != '');
}
function hasBodyHTML() {
return (isset($this->bodyHTML) && $this->bodyHTML != '');
}
function hasAttachments() {
return (isset($this->attachments) && is_array($this->attachments) && count($this->attachments) > 0);
}
function hasImages() {
return (isset($this->images) && is_array($this->images) && count($this->images) > 0);
}
/**
* adds header name/value pair
*
* @param string $name name of header (i.e. what stands before ':')
* @param string $value content of header (i.e. what stands after ':')
* @access public
* @return boolean TRUE optional
*/
function setHeader($name, $value, $replace = TRUE) {
if ($this->hasNoLineBreaks($value)) {
if ($replace && isset($this->headers) && is_array($this->headers)) {
foreach($this->headers as $i => $header) {
list($key, $content) = $header;
if ($key == $name) {
unset($this->headers[$i]);
}
}
}
$this->headers[] = array($name, $value);
} else {
$this->addMsg(MSG_ERROR, "Header contains linebreak chars.");
return FALSE;
}
}
/**
* check for forbidden linebreak chars (header injections)
*
* @param string $str
* @access private
* @return boolean
*/
function hasNoLineBreaks($str) {
if (FALSE === strpos($str, "\n") && FALSE === strpos($str, "\r")) {
return TRUE;
}
return FALSE;
}
/**
* set template markers
*
* @param $tagOpen
* @param $tagClose
* @access public
*/
function setTemplateMarkers($tagOpen, $tagClose) {
$this->tagOpen = $tagOpen;
$this->tagClose = $tagClose;
}
/**
* get template markers
*
* @access public
* @return array (open, close)
*/
function getTemplateMarkers() {
return array($this->tagOpen, $this->tagClose);
}
/**
* replace template patterns with values
*
* @param string $templateString
* @param mixed $fillValues optional, default value NULL
* @access public
* @return string
*/
function replaceTemplateMarkers($templateString, $fillValues = NULL) {
include_once(PAPAYA_INCLUDE_PATH.'system/base_simpletemplate.php');
$template = &new base_simpletemplate();
$template->tagOpen = $this->tagOpen;
$template->tagClose = $this->tagClose;
return $template->parse($templateString, $fillValues);
}
/**
* convert all line breaks to $this->ln
*
* @param string $str
* @access private
* @return string
*/
function reformatLineEnds($str) {
$str = str_replace(array("\r\n", "\r"), "\n", $str);
if ($this->ln != "\n") {
$str = str_replace("\n", $this->ln, $str);
}
return $str;
}
function reWrapQP($str, $lineLength) {
$str = str_replace(array("\r\n", "\r"), "\n", $str);
$lines = explode("\n", $str);
$result = '';
if (isset($lines) && is_array($lines) && count($lines) > 0) {
foreach ($lines as $line) {
$result .= $this->reWrapQPLine($line, $lineLength).$this->ln;
}
}
return $result;
}
function reWrapQPLine($str, $lineLength) {
if (trim($str) == '') {
$result = '';
} elseif (strlen($str) <= $lineLength) {
$result = $str;
} elseif (FALSE !== strpos($str, ' ')) {
$result = $this->reWrapQPLineAtSpace($str, $lineLength);
} elseif (FALSE !== strpos($str, '=')) {
$result = $this->reWrapQPLineAtEqual($str, $lineLength);
} else {
$result = $this->reWrapQPLineAtOffset($str, $lineLength);
}
return $result;
}
function reWrapQPLineAtOffset($str, $lineLength) {
$result = substr(chunk_split($str, $lineLength, '='.$this->ln), 0, -strlen($this->ln) - 1);
return $result;
}
function reWrapQPLineAtSpace($str, $lineLength) {
$result = '';
$buffer = '';
$offset = 0;
$seperator = ' ';
while (FALSE !== ($pos = strpos($str, $seperator, $offset))) {
if ($pos >= $offset) {
$word = substr($str, $offset, $pos - $offset);
if (strlen($word) >= $lineLength) {
if ($buffer != '') {
$result .= $buffer.$seperator.'='.$this->ln;
}
if (FALSE !== strpos($word, '=')) {
$result .= $this->reWrapQPLineAtEqual($word, $lineLength);
} else {
$result .= $this->reWrapQPLineAtOffset($word, $lineLength);
}
$result .= '='.$this->ln;
$buffer = '';
} elseif (strlen($buffer.$word) >= $lineLength) {
$result .= $buffer.$seperator.'='.$this->ln;
$buffer = $word;
} else {
$buffer .= $seperator.$word;
}
$offset = $pos + 1;
} else {
break;
}
}
$word = substr($str, $offset);
if (strlen($word) >= $lineLength) {
$result .= $buffer.$seperator.'='.$this->ln;
if (FALSE !== strpos($word, '=')) {
$result .= $this->reWrapQPLineAtEqual($word, $lineLength);
} else {
$result .= $this->reWrapQPLineAtOffset($word, $lineLength);
}
} elseif (strlen($buffer.$word) >= $lineLength) {
$result .= $buffer.$seperator.'='.$this->ln.$word;
} else {
$result .= $buffer.$seperator.$word;
}
return substr($result, strlen($seperator));
}
function reWrapQPLineAtEqual($str, $lineLength) {
$result = '';
$buffer = '';
$offset = 0;
$seperator = '=';
while (FALSE !== ($pos = strpos($str, $seperator, $offset + 1))) {
if ($pos >= $offset) {
$word = substr($str, $offset, $pos - $offset);
if (strlen($word) >= $lineLength) {
if ($buffer != '') {
$result .= $buffer.'='.$this->ln;
}
$result .= $this->reWrapQPLineAtOffset($word, $lineLength);
$result .= '='.$this->ln;
$buffer = '';
} elseif (strlen($buffer.$word) >= $lineLength) {
$result .= $buffer.'='.$this->ln;
$buffer = $word;
} else {
$buffer .= $word;
}
$offset = $pos;
} else {
break;
}
}
$word = substr($str, $offset);
if (strlen($word) >= $lineLength) {
$result .= $buffer.'='.$this->ln;
$result .= $this->reWrapQPLineAtOffset($word, $lineLength);
} elseif (strlen($buffer.$word) >= $lineLength) {
$result .= $buffer.'='.$this->ln.$word;
} else {
$result .= $buffer.$word;
}
return $result;
}
/**
* checks receipient for linebreak chars or invalid email address format
*
* @param string $to receipient email address
* @access private
* @return string $to receipient email address if valid, else FALSE
*/
function formatAddress($address, $name = '') {
if (trim($name) != '') {
$name = str_replace('\\', '\\', $name);
if (FALSE !== strpos($name, '"')) {
$name = str_replace('"', '\\"', $name);
}
if (FALSE !== strpos($name, ',') || FALSE !== strpos($name, ';')) {
$name = '"'.$name.'"';
}
return $this->encodeHeader($name).' <'.$this->encodePunyCode($address).'>';
} else {
return $this->encodePunyCode($address);
}
}
/**
* convert IDN address to punycode
*
* check email adress for specialchars an try to encode
*
* @param $email
* @access private
* @return string
*/
function encodePunyCode($email) {
$email = papaya_strings::ensureUTF8($email);
if (preg_match('~[^a-zA-Z\d._@-]~', $email)) {
if (function_exists('idn_to_ascii')) {
return idn_to_ascii($email);
} else {
$this->addMsg(MSG_ERROR, 'Invalid email address');
return '';
}
}
return $email;
}
/**
* encodes UTF8 string to RFC-2047 compatible ASCII string
*
* @param string $str string to be encoded
* @access private
* @return string $str encoded string (if $str was not plain ascii)
*/
function encodeHeader($str, $nameOffset = 0) {
if (strlen($str) > (70 - $nameOffset) || preg_match('~[^\x21-\x3C\x3E-\x7E\x09\x20\x0A]~', $str)) {
$maxLength = 70 - $nameOffset - strlen($this->charset);
switch($this->encoding) {
case 'base64' :
$encoded = base64_encode($str);
$maxLength -= $maxLength % 4;
$encStart = '=?'.$this->charset.'?B?';
$encEnd = '?=';
$str = trim(chunk_split($encoded), $maxLength, $encEnd.$this->headerLn.$this->indentChar.$encStart);
$str = $encStart.$str.$encEnd;
break;
case 'quoted-printable' :
default :
$encStart = '=?'.$this->charset.'?Q?';
$encEnd = '?=';
$encoded = $this->encodeQuotedPrintable($str, FALSE, TRUE);
$lines = explode('='.$this->ln, $this->reWrapQPLine($encoded, $maxLength));
$str = '';
foreach ($lines as $line) {
$str .= $this->headerLn.$this->indentChar;
if (FALSE !== strpos($line, '=')) {
$str .= $encStart.chop($line).$encEnd;
} else {
$str .= chop($line);
}
}
$str = substr($str, strlen($this->headerLn.$this->indentChar));
break;
}
}
return $str;
}
/**
* encode a string
*
* @param $str
* @param string $encoding optional, default value 'base64'
* @access private
* @return string
*/
function encodeString($str, $encoding = 'base64') {
$result = '';
switch ($encoding) {
case '8bit' :
$result = $this->reformatLineEnds(wordwrap($str, 72));
if (substr($result, -strlen($this->ln)) != $this->ln) {
$result .= $this->ln;
}
break;
case 'base64' :
$result = chunk_split(base64_encode($str), 76, $this->ln);
break;
case 'quoted-printable' :
$result = $this->encodeQuotedPrintable($str);
if (substr($result, -strlen($this->ln)) != $this->ln) {
$result .= $this->ln;
}
break;
case 'binary' :
return $str;
break;
}
return $result;
}
/**
* encode a file
*
* @param $str
* @param string $encoding optional, default value 'base64'
* @see email::encodeString
* @access private
* @return string
*/
function encodeFile($fileName, $encoding = 'base64') {
if (file_exists($fileName) && is_file($fileName) && is_readable($fileName)) {
$binaryString = file_get_contents($fileName);
return $this->encodeString($binaryString, $encoding);
}
return '';
}
/**
* encode a string using quoted-printable
*
* @param string $str
* @param boolean $wrap - rewrap using soft breaks
* @param boolean $encodeQuestionMark - encode the question mark
* @access private
* @return string
*/
function encodeQuotedPrintable($str, $wrap = TRUE, $encodeQuestionMark = FALSE) {
$result = $str;
$result = preg_replace_callback('~[^\x21-\x3C\x3E-\x7E\x09\x20\x0A\x0D]~',
array(&$this, 'encodeQPChar'), $result);
if ($encodeQuestionMark) {
$result = str_replace('?', '=3F', $result);
}
$result = preg_replace_callback('~[\x09\x20]$~m',
array(&$this, 'encodeQPChar'), $result);
if ($wrap) {
$result = $this->reWrapQP($result, 72);
} else {
$result = $this->reformatLineEnds($result);
}
return $result;
}
/**
* callback encode a special byte using quoted-printable
*
* @param array $match match array from preg_replace_callback
* @access private
* @return string
*/
function encodeQPChar($match) {
$charCode = dechex(ord(substr($match[0], 0, 1)));
return '='.strtoupper(str_pad($charCode, 2, '0', STR_PAD_LEFT));
}
/**
* returns headers formatted
*
* @access private
* @return string $header formatted header
*/
function getHeaders() {
$result = '';
if (isset($this->headers) && is_array($this->headers) && count($this->headers) > 0) {
foreach($this->headers as $entry) {
$result .= $entry[0].': '.$this->encodeHeader($entry[1], strlen($entry[0]))."\n";
}
}
return $result;
}
/**
* returns address headers formatted
*
* @param $addresses
* @param string $headerName To, CC, BCC - can be empty
* @access private
* @return string
*/
function getAddressHeader($addresses, $headerName = '') {
$result = '';
if (isset($addresses) && is_array($addresses) && count($addresses) > 0) {
foreach($addresses as $entry) {
$result .= $this->formatAddress($entry[0], $entry[1]).",".$this->headerLn.$this->indentChar;
}
if (trim($headerName) != '') {
$result = $headerName.': '.substr($result, 0, -strlen($this->headerLn) - 2).$this->headerLn;
} else {
$result = substr($result, 0, -strlen($this->headerLn) - 2);
}
}
return $result;
}
function getAddressEmails($addresses) {
$result = '';
if (isset($addresses) && is_array($addresses) && count($addresses) > 0) {
foreach($addresses as $entry) {
$result .= $this->formatAddress($entry[0]).", ";
}
$result = substr($result, 0, -2);
}
return $result;
}
/**
* get ettachments for message body
*
* @access private
* @return string
*/
function getAttachments($boundary) {
$mimeString = '';
if (isset($this->attachments) && is_array($this->attachments) && count($this->attachments) > 0) {
foreach($this->attachments as $attachment) {
$mimeString .= $this->ln.'--'.$boundary.$this->ln;
$mimeString .= 'Content-Type: '.$attachment['mimetype'].';'.$this->ln.$this->indentChar.
'name="'.$attachment['title'].'"'.$this->ln;
$mimeString .= 'Content-Transfer-Encoding: '.$attachment['encoding'].$this->ln;
$mimeString .= 'Content-Disposition: attachment;'.$this->ln.$this->indentChar.
'filename="'.$attachment['title'].'"'.$this->ln.$this->ln;
if (isset($attachment['file']) && is_readable($attachment['file'])) {
$mimeString .= $this->encodeFile($attachment['file'], 'base64');
} elseif (!empty($attachment['data'])) {
$mimeString .= $this->encodeString($attachment['data'], 'base64');
}
}
}
return $mimeString;
}
/**
* get images for message html part
*
* @access private
* @return string
*/
function getImages($boundary) {
$mimeString = '';
if (isset($this->images) && is_array($this->images) && count($this->images) > 0) {
foreach($this->images as $image) {
$mimeString .= $this->ln.'--'.$boundary.$this->ln;
$mimeString .= 'Content-Type: '.$image['mimetype'].';'.$this->ln.$this->indentChar.
'name="'.$image['title'].'"'.$this->ln;
$mimeString .= 'Content-Transfer-Encoding: '.$image['encoding'].$this->ln;
$mimeString .= 'Content-ID: <'.$image['cid'].'>'.$this->ln;
$mimeString .= 'Content-Disposition: inline;'.$this->ln.$this->indentChar.
'filename="'.$image['title'].'"'.$this->ln.$this->ln;
if (isset($image['file']) && is_readable($image['file'])) {
$mimeString .= $this->encodeFile($image['file'], 'base64');
} elseif (!empty($image['data'])) {
$mimeString .= $this->encodeString($image['data'], 'base64');
}
}
}
return $mimeString;
}
/**
* get a message part start boundary string
*
* @param string $boundary
* @param string $charset
* @param string $contentType
* @param string $encoding
* @access private
* @return string
*/
function getBoundaryStart($boundary, $charset, $contentType, $encoding, $boundaryPart = '') {
if (trim($charset) == '') {
$charset = $this->charset;
}
if (trim($encoding) == '') {
$encoding = $this->encoding;
}
$result = '--'.$boundary.$this->ln;
if ($contentType == 'text/plain' || $contentType == 'text/html') {
$result .= 'Content-Type: '.$contentType.'; charset='.$charset.$this->ln;
$result .= 'Content-Transfer-Encoding: '.$encoding.$this->ln.$this->ln;
} elseif (0 === strpos($contentType, 'multipart')) {
$result .= 'Content-Type: '.$contentType.';'.$this->ln.
$this->indentChar.'boundary="'.$boundaryPart.'"'.$this->ln;
$result .= $this->ln;
} else {
$result .= 'Content-Type: '.$contentType.$this->ln;
$result .= 'Content-Transfer-Encoding: '.$encoding.$this->ln.$this->ln;
}
return $result;
}
/**
* get a message part end boundary string
*
* @param string $boundary
* @access private
* @return string
*/
function getBoundaryEnd($boundary) {
return '--'.$boundary.'--'.$this->ln;
}
/**
* get a new boundary string
*
* @access public
* @return string
*/
function createBoundary() {
$uniqId = md5(rand(0, time()));
return '----'.$uniqId.'.papayaCMS';
}
/**
* set subject or body using template (backwards compatibility)
*
* @see $this->templates
* @param string $name must be 'body', 'body_html' or 'subject' in order to be used
* @access public
*/
function setTemplate($name, $template, $fillValues) {
switch ($name) {
case 'body' :
$this->setBody($template, $fillValues);
return TRUE;
case 'subject' :
$this->setSubject($template, $fillValues);
return TRUE;
default :
$this->addMsg(MSG_WARNING, 'Template must be for body or subject');
return FALSE;
}
}
/**
* returns mailto: link with body and subject to call users email client (use in <a href="%s">link</a>)
*
* @param string $to receipients email adress, may be empty
* @param string $subject subject of message, taken from $this->subject if empty
* @param string $body subject of message, taken from $this->body if empty
* @access public
* @return string mailto string
*/
function getMailtoLink($to = '', $subject = NULL, $body = NULL) {
if (isset($subject)) {
$this->setSubject($subject);
}
if (isset($body)) {
$this->setBody($body);
}
$link = sprintf('mailto:%s?', rawurlencode($to));
if (isset($subject)) {
$link .= sprintf('subject=%s&', $this->encodeHeader($subject));
} elseif ($this->subject != '') {
$link .= sprintf('subject=%s&', $this->encodeHeader($this->subject));
}
if (isset($body)) {
$link .= sprintf('body=%s&', $this->encodeHeader($body));
} elseif ($this->body != '') {
$link .= sprintf('body=%s&', $this->encodeHeader($this->body));
}
return substr($link, 0, -1); // removes trailing ? or &
}
/**
* get the mimetype for a message part
*
* @param boolean $hasText optional, default value FALSE
* @param boolean $hasHTML optional, default value FALSE
* @param boolean $hasImages optional, default value FALSE
* @param boolean $hasAttachments optional, default value FALSE
* @access public
* @return array (boolean $multiPart, string $mimeType)
*/
function getMimeType($hasText = FALSE, $hasHTML = FALSE, $hasImages = FALSE, $hasAttachments = FALSE) {
if ($hasAttachments) {
$type = 'multipart/mixed';
$multiPart = TRUE;
} elseif ($hasImages) {
$type = 'multipart/related';
$multiPart = TRUE;
} elseif ($hasText && $hasHTML) {
$type = 'multipart/alternative';
$multiPart = TRUE;
} elseif ($hasHTML) {
$type = 'text/html';
$multiPart = FALSE;
} else {
$type = 'text/plain';
$multiPart = FALSE;
}
return array('multipart' => $multiPart, 'mimetype' => $type);
}
/**
* get message header and body
*
* @access public
*/
function getMessage($includeTo = FALSE) {
$this->setHeader('User-Agent', 'papaya CMS 5');
$headerStr = $this->getHeaders();
if ($includeTo) {
$headerStr .= $this->getAddressHeader($this->addressTo, 'TO');
}
$headerStr .= $this->getAddressHeader($this->addressCC, 'CC');
$headerStr .= $this->getAddressHeader($this->addressBCC, 'BCC');
if ($from = $this->getSender()) {
$headerStr .= 'From: '.$from.$this->headerLn;
$headerStr .= 'ReplyTo: '.$from.$this->headerLn;
}
$msgType = $this->getMimeType($this->hasBodyText(), $this->hasBodyHTML(),
$this->hasImages(), $this->hasAttachments());
$bodyStr = '';
if ($msgType['multipart']) {
$boundary = $this->createBoundary();
$headerStr .= 'MIME-Version: 1.0'.$this->headerLn;
$headerStr .= 'Content-Type: '.$msgType['mimetype'].';'.$this->headerLn.
$this->indentChar.'boundary="'.$boundary.'"';
$bodyStr .= 'This is a multi-part message in MIME format.'.$this->ln;
$bodyStr .= $this->getMessagePart($msgType, $boundary);
} else {
$headerStr .= 'Content-Type: '.$msgType['mimetype'].'; charset='.$this->charset.$this->headerLn;
if ($this->hasBodyHTML()) {
$headerStr .= 'Content-Transfer-Encoding: '.$this->encodingHTML;
$bodyStr .= $this->encodeString($this->bodyHTML, $this->encodingHTML);
} else {
$headerStr .= 'Content-Transfer-Encoding: '.$this->encodingText;
$bodyStr .= $this->encodeString($this->body, $this->encodingText);
}
}
return array('header' => $headerStr, 'body' => $bodyStr);
}
/**
* get the next part of a multipart message
*
* @param array $msgPartType
* @param string $boundary
* @access public
* @return string
*/
function getMessagePart($msgPartType, $boundary) {
$result = '';
switch ($msgPartType['mimetype']) {
case 'multipart/mixed':
$msgSubPartType = $this->getMimeType($this->hasBodyText(), $this->hasBodyHTML(), $this->hasImages());
$boundarySubPart = $this->createBoundary();
$result .= $this->getBoundaryStart($boundary, '', $msgSubPartType['mimetype'], '', $boundarySubPart);
$result .= $this->getMessagePart($msgSubPartType, $boundarySubPart);
$result .= $this->getAttachments($boundary);
$result .= $this->getBoundaryEnd($boundary).$this->ln;
break;
case 'multipart/related':
$msgSubPartType = $this->getMimeType($this->hasBodyText(), $this->hasBodyHTML());
$boundarySubPart = $this->createBoundary();
if ($msgPartType['multipart']) {
$encoding = $this->encoding;
} else {
$encoding = ($msgSubPartType['mimetype'] == 'text/html')
? $this->encodingHTML : $this->encodingText;
}
$result .= $this->getBoundaryStart($boundary, '', $msgSubPartType['mimetype'], '', $boundarySubPart);
$result .= $this->getMessagePart($msgSubPartType, $boundarySubPart);
$result .= $this->getImages($boundary);
$result .= $this->getBoundaryEnd($boundary).$this->ln;
break;
case 'multipart/alternative' :
$result .= $this->getBoundaryStart($boundary, '', 'text/plain', $this->encodingText);
$result .= $this->encodeString($this->body, $this->encodingText).$this->ln;
$result .= $this->getBoundaryStart($boundary, '', 'text/html', $this->encodingHTML);
$result .= $this->encodeString($this->bodyHTML, $this->encodingHTML).$this->ln;
$result .= $this->getBoundaryEnd($boundary).$this->ln;
break;
case 'text/html' :
$result .= $this->encodeString($this->bodyHTML, $this->encodingHTML).$this->ln;
break;
case 'text/plain' :
$result .= $this->encodeString($this->body, $this->encodingText).$this->ln;
break;
}
return $result;
}
/**
* output the mail to debug this object
*
* @access public
*/
function debugMessage() {
$mailContent = $this->getMessage();
$mailContent['to'] = $this->getAddressHeader($this->addressTo);
$mailContent['subject'] = $this->encodeHeader($this->subject, 8);
base_object::debug($mailContent);
}
/**
* sends email and encodes headers if necessary
* subject or body may be empty if templates are used; if set, they override template settings
*
* @param string $to receipients email adress, taken from $this->addressTo if empty
* @param string $subject subject of message, taken from $this->subject if empty
* @param string $body subject of message, taken from $this->body if empty
* @access public
* @return boolean mail sent
*/
function send($to = NULL, $subject = NULL, $body = NULL) {
if (isset($to)) {
unset($this->addressTo);
$this->addAddress($to);
}
if (isset($subject) && trim($subject) != '') {
$this->setSubject($subject);
}
if (isset($body) && trim($body) != '') {
$this->setBody($body);
}
if (0 === strpos(PHP_OS, 'WIN')) {
$mailContent = $this->getMessage(TRUE);
$result = mail($this->getAddressEmails($this->addressTo),
$this->encodeHeader($this->subject, 8), $mailContent['body'], $mailContent['header']);
} else {
$mailContent = $this->getMessage(FALSE);
$result = mail($this->getAddressHeader($this->addressTo),
$this->encodeHeader($this->subject, 8), $mailContent['body'], $mailContent['header']);
}
if (!$result) {
$this->addMsg(MSG_ERROR, 'Email could not be sent.');
}
return $result;
}
}