Your IP : 52.14.205.130
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2012 Bitrix
*/
namespace Bitrix\Main\Mail;
use Bitrix\Main\Config as Config;
use Bitrix\Main\IO\File;
use Bitrix\Main\Application;
use Bitrix\Main\Web\Uri;
class Mail
{
protected $settingServerMsSmtp;
protected $settingMailFillToEmail;
protected $settingMailConvertMailHeader;
protected $settingMailAddMessageId;
protected $settingConvertNewLineUnixToWindows;
protected $settingMailAdditionalParameters;
protected $settingMaxFileSize;
protected $settingAttachImages;
protected $settingServerName;
protected $settingMailEncodeBase64;
protected $settingMailEncodeQuotedPrintable;
protected $eol;
protected $attachment;
protected $generateTextVersion;
protected $charset;
protected $contentType;
protected $messageId;
protected $filesReplacedFromBody;
protected $trackLinkProtocol;
protected $trackReadLink;
protected $trackClickLink;
protected $trackClickUrlParams;
protected $bitrixDirectory;
protected $trackReadAvailable;
protected $trackClickAvailable;
protected $contentTransferEncoding = '8bit';
protected $to;
protected $subject;
protected $headers = [];
protected $body;
protected $additionalParameters;
/** @var Context */
protected $context;
/** @var Multipart */
protected $multipart;
/** @var Multipart */
protected $multipartRelated;
/** @var array */
protected $blacklistedEmails = [];
/** @var array */
protected $blacklistCheckedEmails = [];
/** @var bool */
protected $useBlacklist = true;
/** @var array */
protected static $emailHeaders = ['to', 'cc', 'bcc'];
/**
* Mail constructor.
*
* @param array $mailParams Mail parameters.
*/
public function __construct(array $mailParams)
{
if(array_key_exists('LINK_PROTOCOL', $mailParams) && $mailParams['LINK_PROTOCOL'] <> '')
{
$this->trackLinkProtocol = $mailParams['LINK_PROTOCOL'];
}
if(array_key_exists('TRACK_READ', $mailParams) && !empty($mailParams['TRACK_READ']))
{
$this->trackReadLink = Tracking::getLinkRead(
$mailParams['TRACK_READ']['MODULE_ID'],
$mailParams['TRACK_READ']['FIELDS'],
$mailParams['TRACK_READ']['URL_PAGE'] ?? null
);
}
if(array_key_exists('TRACK_CLICK', $mailParams) && !empty($mailParams['TRACK_CLICK']))
{
$this->trackClickLink = Tracking::getLinkClick(
$mailParams['TRACK_CLICK']['MODULE_ID'],
$mailParams['TRACK_CLICK']['FIELDS'],
$mailParams['TRACK_CLICK']['URL_PAGE'] ?? null
);
if(!empty($mailParams['TRACK_CLICK']['URL_PARAMS']))
{
$this->trackClickUrlParams = $mailParams['TRACK_CLICK']['URL_PARAMS'];
}
}
if(array_key_exists('LINK_DOMAIN', $mailParams) && $mailParams['LINK_DOMAIN'] <> '')
{
$this->settingServerName = $mailParams['LINK_DOMAIN'];
}
$this->charset = $mailParams['CHARSET'];
$this->contentType = $mailParams['CONTENT_TYPE'];
$this->messageId = $mailParams['MESSAGE_ID'] ?? null;
$this->eol = $this->getMailEol();
$this->attachment = ($mailParams['ATTACHMENT'] ?? array());
if (isset($mailParams['USE_BLACKLIST']))
{
$this->useBlacklist = (bool) $mailParams['USE_BLACKLIST'];
}
$this->initSettings();
if (!$this->trackReadAvailable)
{
$this->trackReadLink = null;
}
if (!$this->trackClickAvailable)
{
$this->trackClickLink = null;
}
if (isset($mailParams['GENERATE_TEXT_VERSION']))
{
$this->generateTextVersion = (bool) $mailParams['GENERATE_TEXT_VERSION'];
}
$this->multipart = (new Multipart())->setContentType(Multipart::MIXED)->setEol($this->eol);
$this->setTo($mailParams['TO']);
$this->setSubject($mailParams['SUBJECT']);
$this->setBody($mailParams['BODY']);
$this->setHeaders($mailParams['HEADER']);
$this->setAdditionalParameters();
if(array_key_exists('CONTEXT', $mailParams) && is_object($mailParams['CONTEXT']))
{
$this->context = $mailParams['CONTEXT'];
}
}
/**
* Create instance.
*
* @param array $mailParams Mail parameters.
* @return static
*/
public static function createInstance(array $mailParams)
{
return new static($mailParams);
}
/**
* Send email.
*
* @param array $mailParams Mail parameters.
* @return bool
*/
public static function send($mailParams)
{
$result = false;
$event = new \Bitrix\Main\Event("main", "OnBeforeMailSend", array($mailParams));
$event->send();
foreach ($event->getResults() as $eventResult)
{
if($eventResult->getType() == \Bitrix\Main\EventResult::ERROR)
return false;
$mailParams = array_merge($mailParams, $eventResult->getParameters());
}
if(defined("ONLY_EMAIL") && $mailParams['TO'] != ONLY_EMAIL)
{
$result = true;
}
else
{
$mail = static::createInstance($mailParams);
if ($mail->canSend())
{
$mailResult = bxmail(
$mail->getTo(),
$mail->getSubject(),
$mail->getBody(),
$mail->getHeaders(),
$mail->getAdditionalParameters(),
$mail->getContext()
);
if($mailResult)
{
$result = true;
}
}
}
return $result;
}
/**
* Return true if mail can be sent.
*
* @return bool
*/
public function canSend()
{
if (empty($this->to))
{
return false;
}
$pseudoHeaders = ['To' => $this->to];
$this->filterHeaderEmails($pseudoHeaders);
return !$this->useBlacklist || !empty($pseudoHeaders);
}
/**
* Init settings.
*
* @return void
*/
public function initSettings()
{
if(defined("BX_MS_SMTP") && BX_MS_SMTP===true)
{
$this->settingServerMsSmtp = true;
}
if(Config\Option::get("main", "fill_to_mail", "N")=="Y")
{
$this->settingMailFillToEmail = true;
}
if(Config\Option::get("main", "convert_mail_header", "Y")=="Y")
{
$this->settingMailConvertMailHeader = true;
}
if(Config\Option::get("main", "send_mid", "N")=="Y")
{
$this->settingMailAddMessageId = true;
}
if(Config\Option::get("main", "CONVERT_UNIX_NEWLINE_2_WINDOWS", "N")=="Y")
{
$this->settingConvertNewLineUnixToWindows = true;
}
if(Config\Option::get("main", "attach_images", "N")=="Y")
{
$this->settingAttachImages = true;
}
if(Config\Option::get("main", "mail_encode_base64", "N") == "Y")
{
$this->settingMailEncodeBase64 = true;
}
else if (Config\Option::get('main', 'mail_encode_quoted_printable', 'N') == 'Y')
{
$this->settingMailEncodeQuotedPrintable = true;
}
if(!isset($this->settingServerName) || $this->settingServerName == '')
{
$this->settingServerName = Config\Option::get("main", "server_name", "");
}
if (!$this->trackLinkProtocol)
{
$this->trackLinkProtocol = Config\Option::get("main", "mail_link_protocol") ?: "http";
}
$this->generateTextVersion = Config\Option::get("main", "mail_gen_text_version", "Y") === 'Y';
$this->settingMaxFileSize = intval(Config\Option::get("main", "max_file_size"));
$this->settingMailAdditionalParameters = Config\Option::get("main", "mail_additional_parameters", "");
$this->bitrixDirectory = Application::getInstance()->getPersonalRoot();
$this->trackReadAvailable = Config\Option::get('main', 'track_outgoing_emails_read', 'Y') == 'Y';
$this->trackClickAvailable = Config\Option::get('main', 'track_outgoing_emails_click', 'Y') == 'Y';
}
/**
* Set additional parameters.
*
* @param string $additionalParameters Additional parameters.
* @return void
*/
public function setAdditionalParameters($additionalParameters = '')
{
$this->additionalParameters = ($additionalParameters ? $additionalParameters : $this->settingMailAdditionalParameters);
}
/**
* Set body.
*
* @param string $bodyPart Html or text of body.
* @return void
*/
public function setBody($bodyPart)
{
$charset = $this->charset;
$messageId = $this->messageId;
$htmlPart = null;
$plainPart = new Part();
$plainPart->addHeader('Content-Type', 'text/plain; charset=' . $charset);
if($this->contentType == "html")
{
$bodyPart = $this->replaceImages($bodyPart);
$bodyPart = $this->replaceHrefs($bodyPart);
$bodyPart = $this->trackRead($bodyPart);
$bodyPart = $this->addMessageIdToBody($bodyPart, true, $messageId);
$htmlPart = new Part();
$htmlPart->addHeader('Content-Type', 'text/html; charset=' . $charset);
$htmlPart->setBody($bodyPart);
$plainPart->setBody(Converter::htmlToText($bodyPart));
}
else
{
$bodyPart = $this->addMessageIdToBody($bodyPart, false, $messageId);
$plainPart->setBody($bodyPart);
}
$cteName = 'Content-Transfer-Encoding';
$cteValue = $this->contentTransferEncoding;
if ($this->settingMailEncodeBase64)
{
$cteValue = 'base64';
}
else if ($this->settingMailEncodeQuotedPrintable)
{
$cteValue = 'quoted-printable';
}
$this->multipart->addHeader($cteName, $cteValue);
$plainPart->addHeader($cteName, $cteValue);
if ($htmlPart)
{
$htmlPart->addHeader($cteName, $cteValue);
}
if ($htmlPart)
{
if ($this->hasImageAttachment(true))
{
$this->multipartRelated = (new Multipart())->setContentType(Multipart::RELATED)->setEol($this->eol);
$this->multipartRelated->addPart($htmlPart);
$htmlPart = $this->multipartRelated;
}
if ($this->generateTextVersion)
{
$alternative = (new Multipart())->setContentType(Multipart::ALTERNATIVE)->setEol($this->eol);
$alternative->addPart($plainPart);
$alternative->addPart($htmlPart);
$this->multipart->addPart($alternative);
}
else
{
$this->multipart->addPart($htmlPart);
}
}
else
{
$this->multipart->addPart($plainPart);
}
$this->setAttachment();
$body = $this->multipart->toStringBody();
$body = str_replace("\r\n", "\n", $body);
if($this->settingConvertNewLineUnixToWindows)
{
$body = str_replace("\n", "\r\n", $body);
}
$this->body = $body;
}
/**
* Return true if mail has attachment.
*
* @return bool
*/
public function hasAttachment()
{
return !empty($this->attachment) || !empty($this->filesReplacedFromBody);
}
/**
* Return true if mail has image attachment.
*
* @param bool $checkRelated Check image as related.
* @return bool
*/
public function hasImageAttachment($checkRelated = false)
{
if (!$this->hasAttachment())
{
return false;
}
$files = $this->attachment;
if(is_array($this->filesReplacedFromBody))
{
$files = array_merge($files, array_values($this->filesReplacedFromBody));
}
foreach($files as $attachment)
{
if ($this->isAttachmentImage($attachment, $checkRelated))
{
return true;
}
}
return false;
}
/**
* Set attachment.
*
* @return void
*/
public function setAttachment()
{
$files = $this->attachment;
if(is_array($this->filesReplacedFromBody))
{
$files = array_merge($files, array_values($this->filesReplacedFromBody));
}
$summarySize = 0;
if(!empty($files))
{
foreach($files as $attachment)
{
$isLimitExceeded = $this->isFileLimitExceeded(
!empty($attachment["SIZE"]) ? $attachment["SIZE"] : strlen($attachment["CONTENT"] ?? ''),
$summarySize
);
if (!$isLimitExceeded)
{
try
{
$fileContent = $attachment["CONTENT"] ?? File::getFileContents($attachment["PATH"]);
}
catch (\Exception $exception)
{
$fileContent = '';
}
}
else
{
$fileContent = '';
}
$isLimitExceeded = $this->isFileLimitExceeded(
strlen($fileContent),
$summarySize
);
if ($isLimitExceeded)
{
$attachment["NAME"] = $attachment["NAME"] . '.txt';
$attachment['CONTENT_TYPE'] = 'text/plain';
$fileContent = str_replace(
['%name%', '%limit%'],
[
$attachment["NAME"],
round($this->settingMaxFileSize / 1024 / 1024, 1),
],
'This is not the original file. The size of the original file `%name%` exceeded the limit of %limit% MB.'
);
}
if(isset($attachment['METHOD']))
{
$name = $this->encodeSubject($attachment["NAME"], $attachment['CHARSET']);
$part = (new Part())
->addHeader('Content-Type', $attachment['CONTENT_TYPE'] .
"; name=\"$name\"; method=".$attachment['METHOD']."; charset=".$attachment['CHARSET'])
->addHeader('Content-Disposition', "attachment; filename=\"$name\"")
->addHeader('Content-Transfer-Encoding', 'base64')
->addHeader('Content-ID', "<{$attachment['ID']}>")
->setBody($fileContent);
}
else
{
$name = $this->encodeSubject($attachment["NAME"], $this->charset);
$part = (new Part())
->addHeader('Content-Type', $attachment['CONTENT_TYPE'] . "; name=\"$name\"")
->addHeader('Content-Disposition', "attachment; filename=\"$name\"")
->addHeader('Content-Transfer-Encoding', 'base64')
->addHeader('Content-ID', "<{$attachment['ID']}>")
->setBody($fileContent);
}
if ($this->multipartRelated && $this->isAttachmentImage($attachment, true))
{
$this->multipartRelated->addPart($part);
}
else
{
$this->multipart->addPart($part);
}
}
}
}
private function isAttachmentImage(&$attachment, $checkRelated = false)
{
if (empty($attachment['CONTENT_TYPE']))
{
return false;
}
if ($checkRelated && empty($attachment['RELATED']))
{
return false;
}
if (mb_strpos($attachment['CONTENT_TYPE'], 'image/') === 0)
{
return true;
}
return false;
}
private function isFileLimitExceeded($fileSize, &$summarySize)
{
// for length after base64
$summarySize += 4 * ceil($fileSize / 3);
return $this->settingMaxFileSize > 0
&& $summarySize > 0
&& $summarySize > $this->settingMaxFileSize;
}
/**
* Set headers.
*
* @param array $headers Headers.
* @return $this
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;
return $this;
}
/**
* Set subject.
*
* @param string $subject Subject.
* @return $this
*/
public function setSubject($subject)
{
$this->subject = $subject;
return $this;
}
/**
* Set to.
*
* @param string $to To.
* @return $this
*/
public function setTo($to)
{
$this->to = $to ? trim($to) : null;
return $this;
}
/**
* Get body.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* Get headers.
*
* @return string
*/
public function getHeaders()
{
$headers = $this->headers;
foreach($headers as $k=>$v)
{
$headers[$k] = trim($v, "\r\n");
if($headers[$k] == '')
{
unset($headers[$k]);
}
}
$this->filterHeaderEmails($headers);
if(
(!isset($headers["Reply-To"]) || $headers["Reply-To"] == '')
&& isset($headers["From"])
&& $headers["From"] <> ''
)
{
$headers["Reply-To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["From"]);
}
if (!isset($headers["X-Priority"]) || $headers["X-Priority"] == '')
{
$headers["X-Priority"] = '3 (Normal)';
}
if(!isset($headers["Date"]) || $headers["Date"] == '')
{
$headers["Date"] = date("r");
}
if(empty($headers["MIME-Version"]))
{
$headers["MIME-Version"] = '1.0';
}
if($this->settingMailConvertMailHeader)
{
foreach($headers as $k => $v)
{
if ($k == 'From' || $k == 'CC' || $k == 'Reply-To')
{
$headers[$k] = $this->encodeHeaderFrom($v, $this->charset);
}
else
{
$headers[$k] = $this->encodeMimeString($v, $this->charset);
}
}
}
if($this->settingServerMsSmtp)
{
if(isset($headers["From"]) && $headers["From"] != '')
{
$headers["From"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["From"]);
}
if(isset($headers["To"]) && $headers["To"] != '')
{
$headers["To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["To"]);
}
if(isset($headers["Reply-To"]) && $headers["Reply-To"] != '')
{
$headers["Reply-To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["Reply-To"]);
}
}
if($this->settingMailFillToEmail)
{
$headers["To"] = $this->getTo();
}
if($this->messageId != '')
{
$headers['X-MID'] = $this->messageId;
}
$headerString = "";
foreach($headers as $k=>$v)
{
$headerString .= $k . ': ' . $v . $this->eol;
}
// Content-Transfer-Encoding & Content-Type add from Multipart
$headerString .= rtrim($this->multipart->toStringHeaders());
return $headerString;
}
/**
* Get message ID.
*
* @return string
*/
public function getMessageId()
{
return $this->messageId;
}
/**
* Get subject.
*
* @return string
*/
public function getSubject()
{
if($this->settingMailConvertMailHeader)
{
return $this->encodeSubject($this->subject, $this->charset);
}
return $this->subject;
}
/**
* Get to.
*
* @return string
*/
public function getTo()
{
$resultTo = static::toPunycode($this->to);
if($this->settingMailConvertMailHeader)
{
$resultTo = static::encodeHeaderFrom($resultTo, $this->charset);
}
if($this->settingServerMsSmtp)
{
$resultTo = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $resultTo);
}
return $resultTo;
}
/**
* Get additional parameters.
*
* @return mixed
*/
public function getAdditionalParameters()
{
return $this->additionalParameters;
}
/**
* Get context instance.
*
* @return Context|null
*/
public function getContext()
{
return $this->context;
}
/**
* Dump email data.
*
* @return string
*/
public function dump()
{
$result = '';
$delimeter = str_repeat('-',5);
$result .= $delimeter."TO".$delimeter."\n".$this->getTo()."\n\n";
$result .= $delimeter."SUBJECT".$delimeter."\n".$this->getSubject()."\n\n";
$result .= $delimeter."HEADERS".$delimeter."\n".$this->getHeaders()."\n\n";
$result .= $delimeter."BODY".$delimeter."\n".$this->getBody()."\n\n";
$result .= $delimeter."ADDITIONAL PARAMETERS".$delimeter."\n".$this->getAdditionalParameters()."\n\n";
return $result;
}
/**
* Return true if input string is in 8bit charset.
*
* @param string $inputString Input string.
* @return bool
*/
public static function is8Bit($inputString)
{
return preg_match("/[\\x80-\\xFF]/", $inputString) > 0;
}
/**
* Encode mime string.
*
* @param string $text Text string.
* @param string $charset Charset.
* @return string
*/
public static function encodeMimeString($text, $charset)
{
if(!static::is8Bit($text))
return $text;
//$maxl = IntVal((76 - strlen($charset) + 7)*0.4);
$res = "";
$maxl = 40;
$eol = static::getMailEol();
$len = mb_strlen($text);
for($i=0; $i<$len; $i=$i+$maxl)
{
if($i>0)
$res .= $eol."\t";
$res .= "=?".$charset."?B?".base64_encode(mb_substr($text, $i, $maxl))."?=";
}
return $res;
}
/**
* Encode subject.
*
* @param string $text Text string.
* @param string $charset Charset.
* @return string
*/
public static function encodeSubject($text, $charset)
{
return "=?".$charset."?B?".base64_encode($text)."?=";
}
/**
* Encode header From.
*
* @param string $text Text string.
* @param string $charset Charset.
* @return string
*/
public static function encodeHeaderFrom($text, $charset)
{
$i = mb_strlen($text);
while($i > 0)
{
if(ord(mb_substr($text, $i - 1, 1))>>7)
break;
$i--;
}
if($i==0)
return $text;
else
return "=?".$charset."?B?".base64_encode(mb_substr($text, 0, $i))."?=".mb_substr($text, $i);
}
/**
* Get symbol of mail End-Of-Line.
*
* @return string
*/
public static function getMailEol()
{
static $eol = false;
if($eol !== false)
{
return $eol;
}
if ((int)(explode('.', phpversion())[0]) >= 8)
{
$eol = "\r\n";
}
elseif(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
{
$eol = "\r\n";
}
elseif(strtoupper(substr(PHP_OS, 0, 3)) <> 'MAC')
{
$eol = "\n"; //unix
}
else
{
$eol = "\r";
}
return $eol;
}
/**
* @param $matches
* @return string
* @throws \Bitrix\Main\IO\FileNotFoundException
*/
protected function getReplacedImageCid($matches)
{
$src = $matches[3];
if($src == "")
{
return $matches[0];
}
if(array_key_exists($src, $this->filesReplacedFromBody))
{
$uid = $this->filesReplacedFromBody[$src]["ID"];
return $matches[1].$matches[2]."cid:".$uid.$matches[4].$matches[5];
}
$uri = new Uri($src);
$filePath = Application::getDocumentRoot() . $uri->getPath();
$io = \CBXVirtualIo::GetInstance();
$filePath = $io->GetPhysicalName($filePath);
if(!File::isFileExists($filePath))
{
return $matches[0];
}
foreach($this->attachment as $attachIndex => $attach)
{
if($filePath == $attach['PATH'])
{
$this->attachment[$attachIndex]['RELATED'] = true;
return $matches[1].$matches[2]."cid:".$attach['ID'].$matches[4].$matches[5];
}
}
if ($this->settingMaxFileSize > 0)
{
$fileIoObject = new File($filePath);
if ($fileIoObject->getSize() > $this->settingMaxFileSize)
{
return $matches[0];
}
}
$imageInfo = (new \Bitrix\Main\File\Image($filePath))->getInfo();
if (!$imageInfo)
{
return $matches[0];
}
if (function_exists("image_type_to_mime_type"))
{
$contentType = image_type_to_mime_type($imageInfo->getFormat());
}
else
{
$contentType = $this->imageTypeToMimeType($imageInfo->getFormat());
}
$uid = uniqid(md5($src));
$this->filesReplacedFromBody[$src] = array(
"RELATED" => true,
"SRC" => $src,
"PATH" => $filePath,
"CONTENT_TYPE" => $contentType,
"NAME" => bx_basename($src),
"ID" => $uid,
);
return $matches[1].$matches[2]."cid:".$uid.$matches[4].$matches[5];
}
/**
* @param $matches
* @return string
*/
protected function getReplacedImageSrc($matches)
{
$src = $matches[3];
if($src == "")
{
return $matches[0];
}
$srcTrimmed = trim($src);
if(mb_substr($srcTrimmed, 0, 2) == "//")
{
$src = $this->trackLinkProtocol . ":" . $srcTrimmed;
}
else if(mb_substr($srcTrimmed, 0, 1) == "/")
{
$srcModified = false;
if(!empty($this->attachment))
{
$io = \CBXVirtualIo::GetInstance();
$filePath = $io->GetPhysicalName(Application::getDocumentRoot().$srcTrimmed);
foreach($this->attachment as $attachIndex => $attach)
{
if($filePath == $attach['PATH'])
{
$this->attachment[$attachIndex]['RELATED'] = true;
$src = "cid:".$attach['ID'];
$srcModified = true;
break;
}
}
}
if(!$srcModified)
{
$src = $this->trackLinkProtocol . "://".$this->settingServerName . $srcTrimmed;
}
}
$add = '';
if (mb_stripos($matches[0], '<img') === 0 && !preg_match("/<img[^>]*?\\s+alt\\s*=[^>]+>/is", $matches[0]))
{
$add = ' alt="" ';
}
return $matches[1] . $matches[2] . $src . $matches[4] . $add . $matches[5];
}
/**
* Replace images.
* All src of images in html will be added by protocol and domain.
*
* @param string $text Html text.
* @return string
*/
public function replaceImages($text)
{
$replaceImageFunction = 'getReplacedImageSrc';
if($this->settingAttachImages)
$replaceImageFunction = 'getReplacedImageCid';
$this->filesReplacedFromBody = array();
$textReplaced = preg_replace_callback(
"/(<img\\s[^>]*?(?<=\\s)src\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
array($this, $replaceImageFunction),
$text
);
if($textReplaced !== null) $text = $textReplaced;
$textReplaced = preg_replace_callback(
"/(background\\s*:\\s*url\\s*\\(|background-image\\s*:\\s*url\\s*\\()([\"']?)(.*?)(\\2)(\\s*\\)(.*?);)/is",
array($this, $replaceImageFunction),
$text
);
if($textReplaced !== null) $text = $textReplaced;
$textReplaced = preg_replace_callback(
"/(<td\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
array($this, $replaceImageFunction),
$text
);
if($textReplaced !== null) $text = $textReplaced;
$textReplaced = preg_replace_callback(
"/(<table\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
array($this, $replaceImageFunction),
$text
);
if($textReplaced !== null) $text = $textReplaced;
return $text;
}
/**
* @param $html
* @return string
*/
private function trackRead($html)
{
if(!$this->trackReadLink)
{
return $html;
}
$url = $this->trackReadLink;
if (mb_substr($url, 0, 4) !== 'http')
{
$url = $this->trackLinkProtocol . "://" . $this->settingServerName . $url;
}
$html .= '<img src="' . $url . '" border="0" height="1" width="1" alt="" />';
return $html;
}
/**
* Replace href attribute in links.
* All href of links in html will be added by protocol and domain.
*
* @param string $text Text.
* @return mixed
*/
public function replaceHrefs($text)
{
if($this->settingServerName != '')
{
$pattern = "/(<a\\s[^>]*?(?<=\\s)href\\s*=\\s*)([\"'])(\\/.*?|http:\\/\\/.*?|https:\\/\\/.*?)(\\2)(\\s.+?>|\\s*>)/is";
$text = preg_replace_callback(
$pattern,
array($this, 'trackClick'),
$text
);
}
return $text;
}
/**
* Track click.
* All href of links in html will be wrapped by tracking url for click-detecting.
*
* @param array $matches Result of preg_match call.
* @return string
*/
public function trackClick($matches)
{
$href = $matches[3];
if ($href == "")
{
return $matches[0];
}
if(mb_substr($href, 0, 2) == '//')
{
$href = $this->trackLinkProtocol . ':' . $href;
}
if(mb_substr($href, 0, 1) == '/')
{
$href = $this->trackLinkProtocol . '://' . $this->settingServerName . $href;
}
if($this->trackClickLink)
{
if($this->trackClickUrlParams)
{
$hrefAddParam = '';
foreach($this->trackClickUrlParams as $k => $v)
$hrefAddParam .= '&'.htmlspecialcharsbx($k).'='.htmlspecialcharsbx($v);
$parsedHref = explode("#", $href);
$parsedHref[0] .= (strpos($parsedHref[0], '?') === false? '?' : '&').mb_substr($hrefAddParam, 1);
$href = implode("#", $parsedHref);
}
$href = $this->trackClickLink . '&url=' . urlencode($href) . '&sign=' . urlencode(Tracking::getSign($href));
if (!preg_match('/^http:\/\/|https:\/\//', $this->trackClickLink))
{
$href = $this->trackLinkProtocol . '://' . $this->settingServerName . $href;
}
}
return $matches[1].$matches[2].$href.$matches[4].$matches[5];
}
/**
* @param $type
* @return string
*/
protected function imageTypeToMimeType($type)
{
$types = array(
1 => "image/gif",
2 => "image/jpeg",
3 => "image/png",
4 => "application/x-shockwave-flash",
5 => "image/psd",
6 => "image/bmp",
7 => "image/tiff",
8 => "image/tiff",
9 => "application/octet-stream",
10 => "image/jp2",
11 => "application/octet-stream",
12 => "application/octet-stream",
13 => "application/x-shockwave-flash",
14 => "image/iff",
15 => "image/vnd.wap.wbmp",
16 => "image/xbm",
);
if(!empty($types[$type]))
return $types[$type];
else
return "application/octet-stream";
}
protected function addMessageIdToBody($body, $isHtml, $messageId)
{
if($this->settingMailAddMessageId && !empty($messageId))
{
$body .= $isHtml ? "<br><br>" : "\n\n";
$body .= "MID #" . $messageId . "\r\n";
}
return $body;
}
/**
* Filter header emails by blacklist.
*
* @param array &$headers Headers.
* return void
*/
protected function filterHeaderEmails(array &$headers)
{
if (!$this->useBlacklist || !Internal\BlacklistTable::hasBlacklistedEmails())
{
return;
}
$list = [];
$allEmails = [mb_strtolower($this->to)];
// get all emails for query Blacklist, prepare emails as Address instances
foreach ($headers as $name => $value)
{
// exclude non target headers
if (!in_array(mb_strtolower($name), static::$emailHeaders))
{
continue;
}
$list[$name] = [];
$emails = explode(',', $value);
foreach ($emails as $email)
{
$email = trim($email);
if (!$email)
{
continue;
}
$address = new Address($email);
$email = $address->getEmail();
if ($email)
{
$list[$name][] = $address;
$allEmails[] = $address->getEmail();
}
}
}
// get blacklisted emails from all emails
$allEmails = array_diff($allEmails, $this->blacklistCheckedEmails);
if (!empty($allEmails))
{
$blacklisted = Internal\BlacklistTable::getList([
'select' => ['CODE'],
'filter' => ['=CODE' => $allEmails]
])->fetchAll();
$blacklisted = array_column($blacklisted, 'CODE');
$this->blacklistedEmails = array_unique(array_merge($this->blacklistedEmails, $blacklisted));
$this->blacklistCheckedEmails = array_merge($this->blacklistCheckedEmails, $allEmails);
}
if (empty($this->blacklistedEmails))
{
return;
}
// remove blacklisted emails, remove empty headers
$blacklisted = $this->blacklistedEmails;
foreach ($headers as $name => $value)
{
// exclude non target headers
if (!in_array(mb_strtolower($name), static::$emailHeaders))
{
continue;
}
// filter Address instances by blacklist
$emails = array_filter(
$list[$name],
function (Address $address) use ($blacklisted)
{
$email = $address->getEmail();
return $email && !in_array($email, $blacklisted);
}
);
// get emails from Address instances
$emails = array_map(
function (Address $address)
{
return $address->getName() ? $address->get() : $address->getEmail();
},
$emails
);
// get header emails as string
$emails = implode(', ', $emails);
// remove empty or update headers
if (!$emails)
{
unset($headers[$name]);
}
else
{
$headers[$name] = $emails;
}
}
}
/**
* Converts an international domain in the email to Punycode.
* @param string $to Email address, possibly with a comment
* @return string
*/
public static function toPunycode($to)
{
$email = $to;
$withComment = false;
if (preg_match("#.*?[<\\[(](.*?)[>\\])].*#i", $to, $matches) && $matches[1] <> '')
{
$email = $matches[1];
$withComment = true;
}
$parts = explode("@", $email);
$domain = $parts[1];
$errors = [];
$domain = \CBXPunycode::ToASCII($domain, $errors);
if (empty($errors))
{
$email = "{$parts[0]}@{$domain}";
if ($withComment)
{
$email = preg_replace("#(.*?)[<\\[(](.*?)[>\\])](.*)#i", '$1<'.$email.'>$3', $to);
}
return $email;
}
return $to;
}
}