9namespace Bitrix\Main\Mail;
11use Bitrix\Main\Config as
Config;
12use Bitrix\Main\IO\File;
13use Bitrix\Main\Application;
14use Bitrix\Main\Web\Uri;
73 if(array_key_exists(
'LINK_PROTOCOL', $mailParams) && $mailParams[
'LINK_PROTOCOL'] <>
'')
75 $this->trackLinkProtocol = $mailParams[
'LINK_PROTOCOL'];
78 if(array_key_exists(
'TRACK_READ', $mailParams) && !empty($mailParams[
'TRACK_READ']))
81 $mailParams[
'TRACK_READ'][
'MODULE_ID'],
82 $mailParams[
'TRACK_READ'][
'FIELDS'],
83 $mailParams[
'TRACK_READ'][
'URL_PAGE'] ??
null
86 if(array_key_exists(
'TRACK_CLICK', $mailParams) && !empty($mailParams[
'TRACK_CLICK']))
89 $mailParams[
'TRACK_CLICK'][
'MODULE_ID'],
90 $mailParams[
'TRACK_CLICK'][
'FIELDS'],
91 $mailParams[
'TRACK_CLICK'][
'URL_PAGE'] ??
null
93 if(!empty($mailParams[
'TRACK_CLICK'][
'URL_PARAMS']))
95 $this->trackClickUrlParams = $mailParams[
'TRACK_CLICK'][
'URL_PARAMS'];
99 if(array_key_exists(
'LINK_DOMAIN', $mailParams) && $mailParams[
'LINK_DOMAIN'] <>
'')
101 $this->settingServerName = $mailParams[
'LINK_DOMAIN'];
104 $this->charset = $mailParams[
'CHARSET'];
105 $this->contentType = $mailParams[
'CONTENT_TYPE'];
106 $this->messageId = $mailParams[
'MESSAGE_ID'] ??
null;
109 $this->attachment = ($mailParams[
'ATTACHMENT'] ??
array());
110 if (isset($mailParams[
'USE_BLACKLIST']))
112 $this->useBlacklist = (bool) $mailParams[
'USE_BLACKLIST'];
117 if (!$this->trackReadAvailable)
119 $this->trackReadLink =
null;
122 if (!$this->trackClickAvailable)
124 $this->trackClickLink =
null;
127 if (isset($mailParams[
'GENERATE_TEXT_VERSION']))
129 $this->generateTextVersion = (bool) $mailParams[
'GENERATE_TEXT_VERSION'];
133 $this->
setTo($mailParams[
'TO']);
135 $this->
setBody($mailParams[
'BODY']);
139 if(array_key_exists(
'CONTEXT', $mailParams) && is_object($mailParams[
'CONTEXT']))
141 $this->context = $mailParams[
'CONTEXT'];
153 return new static($mailParams);
162 public static function send($mailParams)
166 $event = new \Bitrix\Main\Event(
"main",
"OnBeforeMailSend",
array($mailParams));
168 foreach (
$event->getResults() as $eventResult)
170 if($eventResult->getType() == \
Bitrix\
Main\EventResult::ERROR)
173 $mailParams = array_merge($mailParams, $eventResult->getParameters());
176 if(defined(
"ONLY_EMAIL") && $mailParams[
'TO'] != ONLY_EMAIL)
182 $mail = static::createInstance($mailParams);
183 if ($mail->canSend())
190 $mail->getAdditionalParameters(),
211 if (empty($this->to))
216 $pseudoHeaders = [
'To' => $this->to];
219 return !$this->useBlacklist || !empty($pseudoHeaders);
229 if(defined(
"BX_MS_SMTP") && BX_MS_SMTP===
true)
231 $this->settingServerMsSmtp =
true;
234 if(
Config\Option::get(
"main",
"fill_to_mail",
"N")==
"Y")
236 $this->settingMailFillToEmail =
true;
238 if(
Config\Option::get(
"main",
"convert_mail_header",
"Y")==
"Y")
240 $this->settingMailConvertMailHeader =
true;
242 if(
Config\Option::get(
"main",
"send_mid",
"N")==
"Y")
244 $this->settingMailAddMessageId =
true;
246 if(
Config\Option::get(
"main",
"CONVERT_UNIX_NEWLINE_2_WINDOWS",
"N")==
"Y")
248 $this->settingConvertNewLineUnixToWindows =
true;
250 if(
Config\Option::get(
"main",
"attach_images",
"N")==
"Y")
252 $this->settingAttachImages =
true;
254 if(
Config\Option::get(
"main",
"mail_encode_base64",
"N") ==
"Y")
256 $this->settingMailEncodeBase64 =
true;
258 else if (
Config\Option::get(
'main',
'mail_encode_quoted_printable',
'N') ==
'Y')
260 $this->settingMailEncodeQuotedPrintable =
true;
263 if(!isset($this->settingServerName) || $this->settingServerName ==
'')
268 if (!$this->trackLinkProtocol)
270 $this->trackLinkProtocol =
Config\Option::get(
"main",
"mail_link_protocol") ?:
"http";
273 $this->generateTextVersion =
Config\Option::get(
"main",
"mail_gen_text_version",
"Y") ===
'Y';
275 $this->settingMaxFileSize = intval(
Config\Option::get(
"main",
"max_file_size"));
277 $this->settingMailAdditionalParameters =
Config\Option::get(
"main",
"mail_additional_parameters",
"");
281 $this->trackReadAvailable =
Config\Option::get(
'main',
'track_outgoing_emails_read',
'Y') ==
'Y';
282 $this->trackClickAvailable =
Config\Option::get(
'main',
'track_outgoing_emails_click',
'Y') ==
'Y';
309 $plainPart =
new Part();
310 $plainPart->addHeader(
'Content-Type',
'text/plain; charset=' .
$charset);
312 if($this->contentType ==
"html")
316 $bodyPart = $this->trackRead($bodyPart);
319 $htmlPart =
new Part();
320 $htmlPart->addHeader(
'Content-Type',
'text/html; charset=' .
$charset);
321 $htmlPart->setBody($bodyPart);
327 $plainPart->setBody($bodyPart);
330 $cteName =
'Content-Transfer-Encoding';
331 $cteValue = $this->contentTransferEncoding;
333 if ($this->settingMailEncodeBase64)
335 $cteValue =
'base64';
337 else if ($this->settingMailEncodeQuotedPrintable)
339 $cteValue =
'quoted-printable';
342 $this->multipart->addHeader($cteName, $cteValue);
343 $plainPart->addHeader($cteName, $cteValue);
346 $htmlPart->addHeader($cteName, $cteValue);
355 $this->multipartRelated->addPart($htmlPart);
356 $htmlPart = $this->multipartRelated;
359 if ($this->generateTextVersion)
362 $alternative->addPart($plainPart);
363 $alternative->addPart($htmlPart);
364 $this->multipart->addPart($alternative);
368 $this->multipart->addPart($htmlPart);
373 $this->multipart->addPart($plainPart);
378 $body = $this->multipart->toStringBody();
380 if($this->settingConvertNewLineUnixToWindows)
394 return !empty($this->attachment) || !empty($this->filesReplacedFromBody);
410 $files = $this->attachment;
411 if(is_array($this->filesReplacedFromBody))
413 $files = array_merge(
$files, array_values($this->filesReplacedFromBody));
418 if ($this->isAttachmentImage(
$attachment, $checkRelated))
434 $files = $this->attachment;
435 if(is_array($this->filesReplacedFromBody))
437 $files = array_merge(
$files, array_values($this->filesReplacedFromBody));
445 $isLimitExceeded = $this->isFileLimitExceeded(
450 if (!$isLimitExceeded)
456 catch (\Exception $exception)
466 $isLimitExceeded = $this->isFileLimitExceeded(
470 if ($isLimitExceeded)
475 [
'%name%',
'%limit%'],
478 round($this->settingMaxFileSize / 1024 / 1024, 1),
480 'This is not the original file. The size of the original file `%name%` exceeded the limit of %limit% MB.'
488 ->addHeader(
'Content-Type',
$attachment[
'CONTENT_TYPE'] .
490 ->addHeader(
'Content-Disposition',
"attachment; filename=\"$name\"")
491 ->addHeader(
'Content-Transfer-Encoding',
'base64')
492 ->addHeader(
'Content-ID',
"<{$attachment['ID']}>")
499 ->addHeader(
'Content-Type',
$attachment[
'CONTENT_TYPE'] .
"; name=\"$name\"")
500 ->addHeader(
'Content-Disposition',
"attachment; filename=\"$name\"")
501 ->addHeader(
'Content-Transfer-Encoding',
'base64')
502 ->addHeader(
'Content-ID',
"<{$attachment['ID']}>")
506 if ($this->multipartRelated && $this->isAttachmentImage(
$attachment,
true))
508 $this->multipartRelated->addPart($part);
512 $this->multipart->addPart($part);
518 private function isAttachmentImage(&$attachment, $checkRelated =
false)
520 if (empty($attachment[
'CONTENT_TYPE']))
525 if ($checkRelated && empty($attachment[
'RELATED']))
530 if (str_starts_with($attachment[
'CONTENT_TYPE'],
'image/'))
538 private function isFileLimitExceeded($fileSize, &$summarySize)
541 $summarySize += 4 * ceil($fileSize / 3);
543 return $this->settingMaxFileSize > 0
545 && $summarySize > $this->settingMaxFileSize;
580 $this->to =
$to ? trim(
$to) :
null;
620 $headers[
"Reply-To"] = preg_replace(
"/(.*)\\<(.*)\\>/i",
'$2',
$headers[
"From"]);
625 $headers[
"X-Priority"] =
'3 (Normal)';
638 if($this->settingMailConvertMailHeader)
642 if (
$k ==
'From' ||
$k ==
'CC' ||
$k ==
'Reply-To')
653 if($this->settingServerMsSmtp)
657 $headers[
"From"] = preg_replace(
"/(.*)\\<(.*)\\>/i",
'$2',
$headers[
"From"]);
667 $headers[
"Reply-To"] = preg_replace(
"/(.*)\\<(.*)\\>/i",
'$2',
$headers[
"Reply-To"]);
671 if($this->settingMailFillToEmail)
676 if($this->messageId !=
'')
685 $headerString .=
$k .
': ' . $v . $this->eol;
688 $headerString .= rtrim($this->multipart->toStringHeaders());
690 return $headerString;
710 if($this->settingMailConvertMailHeader)
715 return $this->subject;
725 $resultTo = static::toPunycode($this->to);
727 if($this->settingMailConvertMailHeader)
729 $resultTo = static::encodeHeaderFrom($resultTo, $this->charset);
732 if($this->settingServerMsSmtp)
734 $resultTo = preg_replace(
"/(.*)\\<(.*)\\>/i",
'$2', $resultTo);
747 return $this->additionalParameters;
768 $delimeter = str_repeat(
'-',5);
770 $result .= $delimeter.
"TO".$delimeter.
"\n".$this->
getTo().
"\n\n";
773 $result .= $delimeter.
"BODY".$delimeter.
"\n".$this->
getBody().
"\n\n";
786 public static function is8Bit($inputString)
788 return preg_match(
"/[\\x80-\\xFF]/", $inputString) > 0;
800 if(!static::is8Bit(
$text))
806 $eol = static::getMailEol();
807 $len = mb_strlen(
$text);
812 $res .=
"=?".$charset.
"?B?".base64_encode(mb_substr(
$text,
$i, $maxl)).
"?=";
826 return "=?".$charset.
"?B?".base64_encode(
$text).
"?=";
841 if(ord(mb_substr(
$text,
$i - 1, 1))>>7)
848 return "=?".$charset.
"?B?".base64_encode(mb_substr(
$text, 0,
$i)).
"?=".mb_substr(
$text,
$i);
864 if ((
int)(explode(
'.', phpversion())[0]) >= 8)
868 elseif(strtoupper(substr(PHP_OS, 0, 3)) ==
'WIN')
872 elseif(strtoupper(substr(PHP_OS, 0, 3)) <>
'MAC')
899 if(array_key_exists($src, $this->filesReplacedFromBody))
901 $uid = $this->filesReplacedFromBody[$src][
"ID"];
902 return $matches[1].$matches[2].
"cid:".
$uid.$matches[4].$matches[5];
908 $filePath =
$io->GetPhysicalName($filePath);
909 if(!File::isFileExists($filePath))
914 foreach($this->attachment as $attachIndex => $attach)
916 if($filePath == $attach[
'PATH'])
918 $this->attachment[$attachIndex][
'RELATED'] =
true;
919 return $matches[1].$matches[2].
"cid:".$attach[
'ID'].$matches[4].$matches[5];
923 if ($this->settingMaxFileSize > 0)
925 $fileIoObject =
new File($filePath);
926 if ($fileIoObject->getSize() > $this->settingMaxFileSize)
933 $imageInfo = (new \Bitrix\Main\File\Image($filePath))->getInfo();
939 if (function_exists(
"image_type_to_mime_type"))
941 $contentType = image_type_to_mime_type($imageInfo->getFormat());
948 $uid = uniqid(md5($src));
950 $this->filesReplacedFromBody[$src] =
array(
959 return $matches[1].$matches[2].
"cid:".
$uid.$matches[4].$matches[5];
974 $srcTrimmed = trim($src);
975 if(str_starts_with($srcTrimmed,
"//"))
977 $src = $this->trackLinkProtocol .
":" . $srcTrimmed;
979 elseif(str_starts_with($srcTrimmed,
"/"))
981 $srcModified =
false;
982 if(!empty($this->attachment))
986 foreach($this->attachment as $attachIndex => $attach)
988 if($filePath == $attach[
'PATH'])
990 $this->attachment[$attachIndex][
'RELATED'] =
true;
991 $src =
"cid:".$attach[
'ID'];
1000 $src = $this->trackLinkProtocol .
"://".$this->settingServerName . $srcTrimmed;
1005 if (mb_stripos(
$matches[0],
'<img') === 0 && !preg_match(
"/<img[^>]*?\\s+alt\\s*=[^>]+>/is",
$matches[0]))
1022 $replaceImageFunction =
'getReplacedImageSrc';
1023 if($this->settingAttachImages)
1024 $replaceImageFunction =
'getReplacedImageCid';
1026 $this->filesReplacedFromBody =
array();
1027 $textReplaced = preg_replace_callback(
1028 "/(<img\\s[^>]*?(?<=\\s)src\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1029 array($this, $replaceImageFunction),
1032 if($textReplaced !==
null)
$text = $textReplaced;
1034 $textReplaced = preg_replace_callback(
1035 "/(background\\s*:\\s*url\\s*\\(|background-image\\s*:\\s*url\\s*\\()([\"']?)(.*?)(\\2)(\\s*\\)(.*?);)/is",
1036 array($this, $replaceImageFunction),
1039 if($textReplaced !==
null)
$text = $textReplaced;
1041 $textReplaced = preg_replace_callback(
1042 "/(<td\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1043 array($this, $replaceImageFunction),
1046 if($textReplaced !==
null)
$text = $textReplaced;
1048 $textReplaced = preg_replace_callback(
1049 "/(<table\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1050 array($this, $replaceImageFunction),
1053 if($textReplaced !==
null)
$text = $textReplaced;
1062 private function trackRead($html)
1064 if(!$this->trackReadLink)
1069 $url = $this->trackReadLink;
1070 if (!str_starts_with(
$url,
'http'))
1072 $url = $this->trackLinkProtocol .
"://" . $this->settingServerName .
$url;
1075 $html .=
'<img src="' .
$url .
'" border="0" height="1" width="1" alt="" />';
1089 if($this->settingServerName !=
'')
1091 $pattern =
"/(<a\\s[^>]*?(?<=\\s)href\\s*=\\s*)([\"'])(\\/.*?|http:\\/\\/.*?|https:\\/\\/.*?)(\\2)(\\s.+?>|\\s*>)/is";
1092 $text = preg_replace_callback(
1094 array($this,
'trackClick'),
1117 if(str_starts_with($href,
'//'))
1119 $href = $this->trackLinkProtocol .
':' . $href;
1122 if(str_starts_with($href,
'/'))
1124 $href = $this->trackLinkProtocol .
'://' . $this->settingServerName . $href;
1127 if($this->trackClickLink)
1129 if($this->trackClickUrlParams)
1132 foreach($this->trackClickUrlParams as
$k => $v)
1135 $parsedHref = explode(
"#", $href);
1136 $parsedHref[0] .= (!str_contains($parsedHref[0],
'?') ?
'?' :
'&').mb_substr($hrefAddParam, 1);
1137 $href = implode(
"#", $parsedHref);
1140 $href = $this->trackClickLink .
'&url=' . urlencode($href) .
'&sign=' . urlencode(
Tracking::getSign($href));
1141 if (!preg_match(
'/^http:\/\/|https:\/\//', $this->trackClickLink))
1143 $href = $this->trackLinkProtocol .
'://' . $this->settingServerName . $href;
1147 return $matches[1].$matches[2].$href.$matches[4].$matches[5];
1160 4 =>
"application/x-shockwave-flash",
1165 9 =>
"application/octet-stream",
1167 11 =>
"application/octet-stream",
1168 12 =>
"application/octet-stream",
1169 13 =>
"application/x-shockwave-flash",
1171 15 =>
"image/vnd.wap.wbmp",
1174 if(!empty($types[
$type]))
1175 return $types[
$type];
1177 return "application/octet-stream";
1182 if($this->settingMailAddMessageId && !empty(
$messageId))
1184 $body .= $isHtml ?
"<br><br>" :
"\n\n";
1199 if (!$this->useBlacklist || !
Internal\BlacklistTable::hasBlacklistedEmails())
1205 $allEmails = [mb_strtolower($this->to)];
1211 if (!in_array(mb_strtolower(
$name), static::$emailHeaders))
1217 $emails = explode(
',', $value);
1227 $email = $address->getEmail();
1230 $list[
$name][] = $address;
1231 $allEmails[] = $address->getEmail();
1237 $allEmails = array_diff($allEmails, $this->blacklistCheckedEmails);
1238 if (!empty($allEmails))
1241 'select' => [
'CODE'],
1242 'filter' => [
'=CODE' => $allEmails]
1244 $blacklisted = array_column($blacklisted,
'CODE');
1246 $this->blacklistedEmails = array_unique(array_merge($this->blacklistedEmails, $blacklisted));
1247 $this->blacklistCheckedEmails = array_merge($this->blacklistCheckedEmails, $allEmails);
1250 if (empty($this->blacklistedEmails))
1256 $blacklisted = $this->blacklistedEmails;
1260 if (!in_array(mb_strtolower(
$name), static::$emailHeaders))
1267 function (
Address $address) use ($blacklisted)
1303 $withComment =
false;
1308 $withComment =
true;
1311 $parts = explode(
"@",
$email);
1312 $domain = $parts[1];
1319 $email =
"{$parts[0]}@{$domain}";
1323 $email = preg_replace(
"#(.*?)[<\\[(](.*?)[>\\])](.*)#i",
'$1<'.
$email.
'>$3',
$to);
if(! $messageFields||!isset($messageFields['message_id'])||!isset($messageFields['status'])||!CModule::IncludeModule("messageservice")) $messageId
static get($moduleId, $name, $default="", $siteId=false)
static htmlToText(string $body)
__construct(array $mailParams)
static encodeHeaderFrom($text, $charset)
getReplacedImageCid($matches)
setAdditionalParameters($additionalParameters='')
$settingMailAdditionalParameters
static is8Bit($inputString)
addMessageIdToBody($body, $isHtml, $messageId)
imageTypeToMimeType($type)
hasImageAttachment($checkRelated=false)
static encodeMimeString($text, $charset)
$settingMailEncodeQuotedPrintable
filterHeaderEmails(array &$headers)
static createInstance(array $mailParams)
setHeaders(array $headers)
getReplacedImageSrc($matches)
static encodeSubject($text, $charset)
$settingMailConvertMailHeader
getAdditionalParameters()
$settingConvertNewLineUnixToWindows
static getLinkClick($moduleId, $fields, $urlPage=null)
static getLinkRead($moduleId, $fields, $urlPage=null)
static getList(array $parameters=array())
static ToASCII($domainName, &$arErrors)
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
if(file_exists($_SERVER['DOCUMENT_ROOT'] . "/urlrewrite.php")) $uri
bx_basename($path, $ext="")
htmlspecialcharsbx($string, $flags=ENT_COMPAT, $doubleEncode=true)
bxmail($to, $subject, $message, $additional_headers="", $additional_parameters="", Main\Mail\Context $context=null)
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
if(!Loader::includeModule('sale')) $pattern