* @author Thomas Weinert * @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 * @author Thomas Weinert */ 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 '), * 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 () * @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 () * @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 = '~(]+src=)(["\'])(.*?)(\\2)([^>]*>)~i'; $patternBackImg = '~(<(body|table|td)[^>]+background=)(["\'])(.*?)(\\3)([^>]*>)~i'; $patternA = '~(]+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 link) * * @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; } }