'1 (Highest)', 2 => '2 (High)', 3 => '3 (Normal)', 4 => '4 (Low)', 5 => '5 (Lowest)', ]; /** * mbstring.func_overload flag * * @var boolean */ protected static $func_overload; /** * Logger instance to record error messages and awarnings. * * @var \PSR\Log\LoggerInterface */ protected $logger; //-------------------------------------------------------------------- /** * Constructor - Sets Email Preferences * * The constructor can be passed an array of config values * * @param array|null $config */ public function __construct($config = null) { $this->initialize($config); isset(static::$func_overload) || static::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); log_message('info', 'Email Class Initialized'); } //-------------------------------------------------------------------- /** * Initialize preferences * * @param array|\Config\Email $config * * @return Email */ public function initialize($config) { $this->clear(); if ($config instanceof \Config\Email) { $config = get_object_vars($config); } foreach (get_class_vars(get_class($this)) as $key => $value) { if (property_exists($this, $key) && isset($config[$key])) { $method = 'set' . ucfirst($key); if (method_exists($this, $method)) { $this->$method($config[$key]); } else { $this->$key = $config[$key]; } } } $this->charset = strtoupper($this->charset); $this->SMTPAuth = isset($this->SMTPUser[0], $this->SMTPPass[0]); return $this; } //-------------------------------------------------------------------- /** * Initialize the Email Data * * @param boolean $clearAttachments * * @return Email */ public function clear($clearAttachments = false) { $this->subject = ''; $this->body = ''; $this->finalBody = ''; $this->headerStr = ''; $this->replyToFlag = false; $this->recipients = []; $this->CCArray = []; $this->BCCArray = []; $this->headers = []; $this->debugMessage = []; $this->setHeader('Date', $this->setDate()); if ($clearAttachments !== false) { $this->attachments = []; } return $this; } //-------------------------------------------------------------------- /** * Set FROM * * @param string $from * @param string $name * @param string|null $returnPath Return-Path * * @return Email */ public function setFrom($from, $name = '', $returnPath = null) { if (preg_match('/\<(.*)\>/', $from, $match)) { $from = $match[1]; } if ($this->validate) { $this->validateEmail($this->stringToArray($from)); if ($returnPath) { $this->validateEmail($this->stringToArray($returnPath)); } } // prepare the display name if ($name !== '') { // only use Q encoding if there are characters that would require it if (! preg_match('/[\200-\377]/', $name)) { // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes $name = '"' . addcslashes($name, "\0..\37\177'\"\\") . '"'; } else { $name = $this->prepQEncoding($name); } } $this->setHeader('From', $name . ' <' . $from . '>'); isset($returnPath) || $returnPath = $from; $this->setHeader('Return-Path', '<' . $returnPath . '>'); return $this; } //-------------------------------------------------------------------- /** * Set Reply-to * * @param string $replyto * @param string $name * * @return Email */ public function setReplyTo($replyto, $name = '') { if (preg_match('/\<(.*)\>/', $replyto, $match)) { $replyto = $match[1]; } if ($this->validate) { $this->validateEmail($this->stringToArray($replyto)); } if ($name !== '') { // only use Q encoding if there are characters that would require it if (! preg_match('/[\200-\377]/', $name)) { // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes $name = '"' . addcslashes($name, "\0..\37\177'\"\\") . '"'; } else { $name = $this->prepQEncoding($name); } } $this->setHeader('Reply-To', $name . ' <' . $replyto . '>'); $this->replyToFlag = true; return $this; } //-------------------------------------------------------------------- /** * Set Recipients * * @param string $to * * @return Email */ public function setTo($to) { $to = $this->stringToArray($to); $to = $this->cleanEmail($to); if ($this->validate) { $this->validateEmail($to); } if ($this->getProtocol() !== 'mail') { $this->setHeader('To', implode(', ', $to)); } $this->recipients = $to; return $this; } //-------------------------------------------------------------------- /** * Set CC * * @param string $cc * * @return Email */ public function setCC($cc) { $cc = $this->cleanEmail($this->stringToArray($cc)); if ($this->validate) { $this->validateEmail($cc); } $this->setHeader('Cc', implode(', ', $cc)); if ($this->getProtocol() === 'smtp') { $this->CCArray = $cc; } return $this; } //-------------------------------------------------------------------- /** * Set BCC * * @param string $bcc * @param string $limit * * @return Email */ public function setBCC($bcc, $limit = '') { if ($limit !== '' && is_numeric($limit)) { $this->BCCBatchMode = true; $this->BCCBatchSize = $limit; } $bcc = $this->cleanEmail($this->stringToArray($bcc)); if ($this->validate) { $this->validateEmail($bcc); } if ($this->getProtocol() === 'smtp' || ($this->BCCBatchMode && count($bcc) > $this->BCCBatchSize)) { $this->BCCArray = $bcc; } else { $this->setHeader('Bcc', implode(', ', $bcc)); } return $this; } //-------------------------------------------------------------------- /** * Set Email Subject * * @param string $subject * * @return Email */ public function setSubject($subject) { $subject = $this->prepQEncoding($subject); $this->setHeader('Subject', $subject); return $this; } //-------------------------------------------------------------------- /** * Set Body * * @param string $body * * @return Email */ public function setMessage($body) { $this->body = rtrim(str_replace("\r", '', $body)); return $this; } //-------------------------------------------------------------------- /** * Assign file attachments * * @param string $file Can be local path, URL or buffered content * @param string $disposition 'attachment' * @param string|null $newname * @param string $mime * * @return Email */ public function attach($file, $disposition = '', $newname = null, $mime = '') { if ($mime === '') { if (strpos($file, '://') === false && ! is_file($file)) { $this->setErrorMessage(lang('Email.attachmentMissing', [$file])); return false; } if (! $fp = @fopen($file, 'rb')) { $this->setErrorMessage(lang('Email.attachmentUnreadable', [$file])); return false; } $fileContent = stream_get_contents($fp); $mime = $this->mimeTypes(pathinfo($file, PATHINFO_EXTENSION)); fclose($fp); } else { $fileContent = & $file; // buffered file } // declare names on their own, to make phpcbf happy $namesAttached = [ $file, $newname, ]; $this->attachments[] = [ 'name' => $namesAttached, 'disposition' => empty($disposition) ? 'attachment' : $disposition, // Can also be 'inline' Not sure if it matters 'type' => $mime, 'content' => chunk_split(base64_encode($fileContent)), 'multipart' => 'mixed', ]; return $this; } //-------------------------------------------------------------------- /** * Set and return attachment Content-ID * * Useful for attached inline pictures * * @param string $filename * * @return string */ public function setAttachmentCID($filename) { for ($i = 0, $c = count($this->attachments); $i < $c; $i ++) { if ($this->attachments[$i]['name'][0] === $filename) { $this->attachments[$i]['multipart'] = 'related'; $this->attachments[$i]['cid'] = uniqid(basename($this->attachments[$i]['name'][0]) . '@', true); return $this->attachments[$i]['cid']; } } return false; } //-------------------------------------------------------------------- /** * Add a Header Item * * @param string $header * @param string $value * * @return Email */ public function setHeader($header, $value) { $this->headers[$header] = str_replace(["\n", "\r"], '', $value); return $this; } //-------------------------------------------------------------------- /** * Convert a String to an Array * * @param string $email * * @return array */ protected function stringToArray($email) { if (! is_array($email)) { return (strpos($email, ',') !== false) ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY) : (array) trim($email); } return $email; } //-------------------------------------------------------------------- /** * Set Multipart Value * * @param string $str * * @return Email */ public function setAltMessage($str) { $this->altMessage = (string) $str; return $this; } //-------------------------------------------------------------------- /** * Set Mailtype * * @param string $type * * @return Email */ public function setMailType($type = 'text') { $this->mailType = ($type === 'html') ? 'html' : 'text'; return $this; } //-------------------------------------------------------------------- /** * Set Wordwrap * * @param boolean $wordWrap * * @return Email */ public function setWordWrap($wordWrap = true) { $this->wordWrap = (bool) $wordWrap; return $this; } //-------------------------------------------------------------------- /** * Set Protocol * * @param string $protocol * * @return Email */ public function setProtocol($protocol = 'mail') { $this->protocol = in_array($protocol, $this->protocols, true) ? strtolower($protocol) : 'mail'; return $this; } //-------------------------------------------------------------------- /** * Set Priority * * @param integer $n * * @return Email */ public function setPriority($n = 3) { $this->priority = preg_match('/^[1-5]$/', $n) ? (int) $n : 3; return $this; } //-------------------------------------------------------------------- /** * Set Newline Character * * @param string $newline * * @return Email */ public function setNewline($newline = "\n") { $this->newline = in_array($newline, ["\n", "\r\n", "\r"]) ? $newline : "\n"; return $this; } //-------------------------------------------------------------------- /** * Set CRLF * * @param string $CRLF * * @return Email */ public function setCRLF($CRLF = "\n") { $this->CRLF = ($CRLF !== "\n" && $CRLF !== "\r\n" && $CRLF !== "\r") ? "\n" : $CRLF; return $this; } //-------------------------------------------------------------------- /** * Get the Message ID * * @return string */ protected function getMessageID() { $from = str_replace(['>', '<'], '', $this->headers['Return-Path']); return '<' . uniqid('', true) . strstr($from, '@') . '>'; } //-------------------------------------------------------------------- /** * Get Mail Protocol * * @return string */ protected function getProtocol() { $this->protocol = strtolower($this->protocol); in_array($this->protocol, $this->protocols, true) || $this->protocol = 'mail'; return $this->protocol; } //-------------------------------------------------------------------- /** * Get Mail Encoding * * @return string */ protected function getEncoding() { in_array($this->encoding, $this->bitDepths) || $this->encoding = '8bit'; foreach ($this->baseCharsets as $charset) { if (strpos($this->charset, $charset) === 0) { $this->encoding = '7bit'; break; } } return $this->encoding; } //-------------------------------------------------------------------- /** * Get content type (text/html/attachment) * * @return string */ protected function getContentType() { if ($this->mailType === 'html') { return empty($this->attachments) ? 'html' : 'html-attach'; } elseif ($this->mailType === 'text' && ! empty($this->attachments)) { return 'plain-attach'; } else { return 'plain'; } } //-------------------------------------------------------------------- /** * Set RFC 822 Date * * @return string */ protected function setDate() { $timezone = date('Z'); $operator = ($timezone[0] === '-') ? '-' : '+'; $timezone = abs($timezone); $timezone = floor($timezone / 3600) * 100 + ($timezone % 3600) / 60; return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone); } //-------------------------------------------------------------------- /** * Mime message * * @return string */ protected function getMimeMessage() { return 'This is a multi-part message in MIME format.' . $this->newline . 'Your email application may not support this format.'; } //-------------------------------------------------------------------- /** * Validate Email Address * * @param string $email * * @return boolean */ public function validateEmail($email) { if (! is_array($email)) { $this->setErrorMessage(lang('Email.mustBeArray')); return false; } foreach ($email as $val) { if (! $this->isValidEmail($val)) { $this->setErrorMessage(lang('Email.invalidAddress', $val)); return false; } } return true; } //-------------------------------------------------------------------- /** * Email Validation * * @param string $email * * @return boolean */ public function isValidEmail($email) { if (function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46') && $atpos = strpos($email, '@')) { $email = static::substr($email, 0, ++ $atpos) . idn_to_ascii( static::substr($email, $atpos), 0, INTL_IDNA_VARIANT_UTS46 ); } return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); } //-------------------------------------------------------------------- /** * Clean Extended Email Address: Joe Smith * * @param string $email * * @return string */ public function cleanEmail($email) { if (! is_array($email)) { return preg_match('/\<(.*)\>/', $email, $match) ? $match[1] : $email; } $cleanEmail = []; foreach ($email as $addy) { $cleanEmail[] = preg_match('/\<(.*)\>/', $addy, $match) ? $match[1] : $addy; } return $cleanEmail; } //-------------------------------------------------------------------- /** * Build alternative plain text message * * Provides the raw message for use in plain-text headers of * HTML-formatted emails. * If the user hasn't specified his own alternative message * it creates one by stripping the HTML * * @return string */ protected function getAltMessage() { if (! empty($this->altMessage)) { return ($this->wordWrap) ? $this->wordWrap($this->altMessage, 76) : $this->altMessage; } $body = preg_match('/\(.*)\<\/body\>/si', $this->body, $match) ? $match[1] : $this->body; $body = str_replace("\t", '', preg_replace('#