Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
mail.php
1<?php
9namespace Bitrix\Main\Mail;
10
15
16class Mail
17{
29
30 protected $eol;
31 protected $attachment;
33 protected $charset;
34 protected $contentType;
35 protected $messageId;
38 protected $trackReadLink;
39 protected $trackClickLink;
44
45 protected $contentTransferEncoding = '8bit';
46 protected $to;
47 protected $subject;
48 protected $headers = [];
49 protected $body;
52 protected $context;
54 protected $multipart;
58 protected $blacklistedEmails = [];
62 protected $useBlacklist = true;
64 protected static $emailHeaders = ['to', 'cc', 'bcc'];
65
71 public function __construct(array $mailParams)
72 {
73 if(array_key_exists('LINK_PROTOCOL', $mailParams) && $mailParams['LINK_PROTOCOL'] <> '')
74 {
75 $this->trackLinkProtocol = $mailParams['LINK_PROTOCOL'];
76 }
77
78 if(array_key_exists('TRACK_READ', $mailParams) && !empty($mailParams['TRACK_READ']))
79 {
80 $this->trackReadLink = Tracking::getLinkRead(
81 $mailParams['TRACK_READ']['MODULE_ID'],
82 $mailParams['TRACK_READ']['FIELDS'],
83 $mailParams['TRACK_READ']['URL_PAGE'] ?? null
84 );
85 }
86 if(array_key_exists('TRACK_CLICK', $mailParams) && !empty($mailParams['TRACK_CLICK']))
87 {
88 $this->trackClickLink = Tracking::getLinkClick(
89 $mailParams['TRACK_CLICK']['MODULE_ID'],
90 $mailParams['TRACK_CLICK']['FIELDS'],
91 $mailParams['TRACK_CLICK']['URL_PAGE'] ?? null
92 );
93 if(!empty($mailParams['TRACK_CLICK']['URL_PARAMS']))
94 {
95 $this->trackClickUrlParams = $mailParams['TRACK_CLICK']['URL_PARAMS'];
96 }
97 }
98
99 if(array_key_exists('LINK_DOMAIN', $mailParams) && $mailParams['LINK_DOMAIN'] <> '')
100 {
101 $this->settingServerName = $mailParams['LINK_DOMAIN'];
102 }
103
104 $this->charset = $mailParams['CHARSET'];
105 $this->contentType = $mailParams['CONTENT_TYPE'];
106 $this->messageId = $mailParams['MESSAGE_ID'] ?? null;
107 $this->eol = $this->getMailEol();
108
109 $this->attachment = ($mailParams['ATTACHMENT'] ?? array());
110 if (isset($mailParams['USE_BLACKLIST']))
111 {
112 $this->useBlacklist = (bool) $mailParams['USE_BLACKLIST'];
113 }
114
115 $this->initSettings();
116
117 if (!$this->trackReadAvailable)
118 {
119 $this->trackReadLink = null;
120 }
121
122 if (!$this->trackClickAvailable)
123 {
124 $this->trackClickLink = null;
125 }
126
127 if (isset($mailParams['GENERATE_TEXT_VERSION']))
128 {
129 $this->generateTextVersion = (bool) $mailParams['GENERATE_TEXT_VERSION'];
130 }
131 $this->multipart = (new Multipart())->setContentType(Multipart::MIXED)->setEol($this->eol);
132
133 $this->setTo($mailParams['TO']);
134 $this->setSubject($mailParams['SUBJECT']);
135 $this->setBody($mailParams['BODY']);
136 $this->setHeaders($mailParams['HEADER']);
138
139 if(array_key_exists('CONTEXT', $mailParams) && is_object($mailParams['CONTEXT']))
140 {
141 $this->context = $mailParams['CONTEXT'];
142 }
143 }
144
151 public static function createInstance(array $mailParams)
152 {
153 return new static($mailParams);
154 }
155
162 public static function send($mailParams)
163 {
164 $result = false;
165
166 $event = new \Bitrix\Main\Event("main", "OnBeforeMailSend", array($mailParams));
167 $event->send();
168 foreach ($event->getResults() as $eventResult)
169 {
170 if($eventResult->getType() == \Bitrix\Main\EventResult::ERROR)
171 return false;
172
173 $mailParams = array_merge($mailParams, $eventResult->getParameters());
174 }
175
176 if(defined("ONLY_EMAIL") && $mailParams['TO'] != ONLY_EMAIL)
177 {
178 $result = true;
179 }
180 else
181 {
182 $mail = static::createInstance($mailParams);
183 if ($mail->canSend())
184 {
185 $mailResult = bxmail(
186 $mail->getTo(),
187 $mail->getSubject(),
188 $mail->getBody(),
189 $mail->getHeaders(),
190 $mail->getAdditionalParameters(),
191 $mail->getContext()
192 );
193
194 if($mailResult)
195 {
196 $result = true;
197 }
198 }
199 }
200
201 return $result;
202 }
203
209 public function canSend()
210 {
211 if (empty($this->to))
212 {
213 return false;
214 }
215
216 $pseudoHeaders = ['To' => $this->to];
217 $this->filterHeaderEmails($pseudoHeaders);
218
219 return !$this->useBlacklist || !empty($pseudoHeaders);
220 }
221
227 public function initSettings()
228 {
229 if(defined("BX_MS_SMTP") && BX_MS_SMTP===true)
230 {
231 $this->settingServerMsSmtp = true;
232 }
233
234 if(Config\Option::get("main", "fill_to_mail", "N")=="Y")
235 {
236 $this->settingMailFillToEmail = true;
237 }
238 if(Config\Option::get("main", "convert_mail_header", "Y")=="Y")
239 {
240 $this->settingMailConvertMailHeader = true;
241 }
242 if(Config\Option::get("main", "send_mid", "N")=="Y")
243 {
244 $this->settingMailAddMessageId = true;
245 }
246 if(Config\Option::get("main", "CONVERT_UNIX_NEWLINE_2_WINDOWS", "N")=="Y")
247 {
248 $this->settingConvertNewLineUnixToWindows = true;
249 }
250 if(Config\Option::get("main", "attach_images", "N")=="Y")
251 {
252 $this->settingAttachImages = true;
253 }
254 if(Config\Option::get("main", "mail_encode_base64", "N") == "Y")
255 {
256 $this->settingMailEncodeBase64 = true;
257 }
258 else if (Config\Option::get('main', 'mail_encode_quoted_printable', 'N') == 'Y')
259 {
260 $this->settingMailEncodeQuotedPrintable = true;
261 }
262
263 if(!isset($this->settingServerName) || $this->settingServerName == '')
264 {
265 $this->settingServerName = Config\Option::get("main", "server_name", "");
266 }
267
268 if (!$this->trackLinkProtocol)
269 {
270 $this->trackLinkProtocol = Config\Option::get("main", "mail_link_protocol") ?: "http";
271 }
272
273 $this->generateTextVersion = Config\Option::get("main", "mail_gen_text_version", "Y") === 'Y';
274
275 $this->settingMaxFileSize = intval(Config\Option::get("main", "max_file_size"));
276
277 $this->settingMailAdditionalParameters = Config\Option::get("main", "mail_additional_parameters", "");
278
279 $this->bitrixDirectory = Application::getInstance()->getPersonalRoot();
280
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';
283 }
284
291 public function setAdditionalParameters($additionalParameters = '')
292 {
293 $this->additionalParameters = ($additionalParameters ? $additionalParameters : $this->settingMailAdditionalParameters);
294 }
295
296
303 public function setBody($bodyPart)
304 {
305 $charset = $this->charset;
306 $messageId = $this->messageId;
307
308 $htmlPart = null;
309 $plainPart = new Part();
310 $plainPart->addHeader('Content-Type', 'text/plain; charset=' . $charset);
311
312 if($this->contentType == "html")
313 {
314 $bodyPart = $this->replaceImages($bodyPart);
315 $bodyPart = $this->replaceHrefs($bodyPart);
316 $bodyPart = $this->trackRead($bodyPart);
317 $bodyPart = $this->addMessageIdToBody($bodyPart, true, $messageId);
318
319 $htmlPart = new Part();
320 $htmlPart->addHeader('Content-Type', 'text/html; charset=' . $charset);
321 $htmlPart->setBody($bodyPart);
322 $plainPart->setBody(Converter::htmlToText($bodyPart));
323 }
324 else
325 {
326 $bodyPart = $this->addMessageIdToBody($bodyPart, false, $messageId);
327 $plainPart->setBody($bodyPart);
328 }
329
330 $cteName = 'Content-Transfer-Encoding';
331 $cteValue = $this->contentTransferEncoding;
332
333 if ($this->settingMailEncodeBase64)
334 {
335 $cteValue = 'base64';
336 }
337 else if ($this->settingMailEncodeQuotedPrintable)
338 {
339 $cteValue = 'quoted-printable';
340 }
341
342 $this->multipart->addHeader($cteName, $cteValue);
343 $plainPart->addHeader($cteName, $cteValue);
344 if ($htmlPart)
345 {
346 $htmlPart->addHeader($cteName, $cteValue);
347 }
348
349
350 if ($htmlPart)
351 {
352 if ($this->hasImageAttachment(true))
353 {
354 $this->multipartRelated = (new Multipart())->setContentType(Multipart::RELATED)->setEol($this->eol);
355 $this->multipartRelated->addPart($htmlPart);
356 $htmlPart = $this->multipartRelated;
357 }
358
359 if ($this->generateTextVersion)
360 {
361 $alternative = (new Multipart())->setContentType(Multipart::ALTERNATIVE)->setEol($this->eol);
362 $alternative->addPart($plainPart);
363 $alternative->addPart($htmlPart);
364 $this->multipart->addPart($alternative);
365 }
366 else
367 {
368 $this->multipart->addPart($htmlPart);
369 }
370 }
371 else
372 {
373 $this->multipart->addPart($plainPart);
374 }
375
376 $this->setAttachment();
377
378 $body = $this->multipart->toStringBody();
379 $body = str_replace("\r\n", "\n", $body);
380 if($this->settingConvertNewLineUnixToWindows)
381 {
382 $body = str_replace("\n", "\r\n", $body);
383 }
384 $this->body = $body;
385 }
386
392 public function hasAttachment()
393 {
394 return !empty($this->attachment) || !empty($this->filesReplacedFromBody);
395 }
396
403 public function hasImageAttachment($checkRelated = false)
404 {
405 if (!$this->hasAttachment())
406 {
407 return false;
408 }
409
410 $files = $this->attachment;
411 if(is_array($this->filesReplacedFromBody))
412 {
413 $files = array_merge($files, array_values($this->filesReplacedFromBody));
414 }
415
416 foreach($files as $attachment)
417 {
418 if ($this->isAttachmentImage($attachment, $checkRelated))
419 {
420 return true;
421 }
422 }
423
424 return false;
425 }
426
432 public function setAttachment()
433 {
434 $files = $this->attachment;
435 if(is_array($this->filesReplacedFromBody))
436 {
437 $files = array_merge($files, array_values($this->filesReplacedFromBody));
438 }
439
440 $summarySize = 0;
441 if(!empty($files))
442 {
443 foreach($files as $attachment)
444 {
445 $isLimitExceeded = $this->isFileLimitExceeded(
446 !empty($attachment["SIZE"]) ? $attachment["SIZE"] : strlen($attachment["CONTENT"] ?? ''),
447 $summarySize
448 );
449
450 if (!$isLimitExceeded)
451 {
452 try
453 {
454 $fileContent = $attachment["CONTENT"] ?? File::getFileContents($attachment["PATH"]);
455 }
456 catch (\Exception $exception)
457 {
458 $fileContent = '';
459 }
460 }
461 else
462 {
463 $fileContent = '';
464 }
465
466 $isLimitExceeded = $this->isFileLimitExceeded(
467 strlen($fileContent),
468 $summarySize
469 );
470 if ($isLimitExceeded)
471 {
472 $attachment["NAME"] = $attachment["NAME"] . '.txt';
473 $attachment['CONTENT_TYPE'] = 'text/plain';
474 $fileContent = str_replace(
475 ['%name%', '%limit%'],
476 [
477 $attachment["NAME"],
478 round($this->settingMaxFileSize / 1024 / 1024, 1),
479 ],
480 'This is not the original file. The size of the original file `%name%` exceeded the limit of %limit% MB.'
481 );
482 }
483
484 if(isset($attachment['METHOD']))
485 {
486 $name = $this->encodeSubject($attachment["NAME"], $attachment['CHARSET']);
487 $part = (new Part())
488 ->addHeader('Content-Type', $attachment['CONTENT_TYPE'] .
489 "; name=\"$name\"; method=".$attachment['METHOD']."; charset=".$attachment['CHARSET'])
490 ->addHeader('Content-Disposition', "attachment; filename=\"$name\"")
491 ->addHeader('Content-Transfer-Encoding', 'base64')
492 ->addHeader('Content-ID', "<{$attachment['ID']}>")
493 ->setBody($fileContent);
494 }
495 else
496 {
497 $name = $this->encodeSubject($attachment["NAME"], $this->charset);
498 $part = (new Part())
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']}>")
503 ->setBody($fileContent);
504 }
505
506 if ($this->multipartRelated && $this->isAttachmentImage($attachment, true))
507 {
508 $this->multipartRelated->addPart($part);
509 }
510 else
511 {
512 $this->multipart->addPart($part);
513 }
514 }
515 }
516 }
517
518 private function isAttachmentImage(&$attachment, $checkRelated = false)
519 {
520 if (empty($attachment['CONTENT_TYPE']))
521 {
522 return false;
523 }
524
525 if ($checkRelated && empty($attachment['RELATED']))
526 {
527 return false;
528 }
529
530 if (mb_strpos($attachment['CONTENT_TYPE'], 'image/') === 0)
531 {
532 return true;
533 }
534
535 return false;
536 }
537
538 private function isFileLimitExceeded($fileSize, &$summarySize)
539 {
540 // for length after base64
541 $summarySize += 4 * ceil($fileSize / 3);
542
543 return $this->settingMaxFileSize > 0
544 && $summarySize > 0
545 && $summarySize > $this->settingMaxFileSize;
546 }
547
554 public function setHeaders(array $headers)
555 {
556 $this->headers = $headers;
557 return $this;
558 }
559
566 public function setSubject($subject)
567 {
568 $this->subject = $subject;
569 return $this;
570 }
571
578 public function setTo($to)
579 {
580 $this->to = $to ? trim($to) : null;
581 return $this;
582 }
583
589 public function getBody()
590 {
591 return $this->body;
592 }
593
599 public function getHeaders()
600 {
601 $headers = $this->headers;
602
603 foreach($headers as $k=>$v)
604 {
605 $headers[$k] = trim($v, "\r\n");
606 if($headers[$k] == '')
607 {
608 unset($headers[$k]);
609 }
610 }
611
613
614 if(
615 (!isset($headers["Reply-To"]) || $headers["Reply-To"] == '')
616 && isset($headers["From"])
617 && $headers["From"] <> ''
618 )
619 {
620 $headers["Reply-To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["From"]);
621 }
622
623 if (!isset($headers["X-Priority"]) || $headers["X-Priority"] == '')
624 {
625 $headers["X-Priority"] = '3 (Normal)';
626 }
627
628 if(!isset($headers["Date"]) || $headers["Date"] == '')
629 {
630 $headers["Date"] = date("r");
631 }
632
633 if(empty($headers["MIME-Version"]))
634 {
635 $headers["MIME-Version"] = '1.0';
636 }
637
638 if($this->settingMailConvertMailHeader)
639 {
640 foreach($headers as $k => $v)
641 {
642 if ($k == 'From' || $k == 'CC' || $k == 'Reply-To')
643 {
644 $headers[$k] = $this->encodeHeaderFrom($v, $this->charset);
645 }
646 else
647 {
648 $headers[$k] = $this->encodeMimeString($v, $this->charset);
649 }
650 }
651 }
652
653 if($this->settingServerMsSmtp)
654 {
655 if(isset($headers["From"]) && $headers["From"] != '')
656 {
657 $headers["From"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["From"]);
658 }
659
660 if(isset($headers["To"]) && $headers["To"] != '')
661 {
662 $headers["To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["To"]);
663 }
664
665 if(isset($headers["Reply-To"]) && $headers["Reply-To"] != '')
666 {
667 $headers["Reply-To"] = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $headers["Reply-To"]);
668 }
669 }
670
671 if($this->settingMailFillToEmail)
672 {
673 $headers["To"] = $this->getTo();
674 }
675
676 if($this->messageId != '')
677 {
678 $headers['X-MID'] = $this->messageId;
679 }
680
681
682 $headerString = "";
683 foreach($headers as $k=>$v)
684 {
685 $headerString .= $k . ': ' . $v . $this->eol;
686 }
687 // Content-Transfer-Encoding & Content-Type add from Multipart
688 $headerString .= rtrim($this->multipart->toStringHeaders());
689
690 return $headerString;
691 }
692
698 public function getMessageId()
699 {
700 return $this->messageId;
701 }
702
708 public function getSubject()
709 {
710 if($this->settingMailConvertMailHeader)
711 {
712 return $this->encodeSubject($this->subject, $this->charset);
713 }
714
715 return $this->subject;
716 }
717
723 public function getTo()
724 {
725 $resultTo = static::toPunycode($this->to);
726
727 if($this->settingMailConvertMailHeader)
728 {
729 $resultTo = static::encodeHeaderFrom($resultTo, $this->charset);
730 }
731
732 if($this->settingServerMsSmtp)
733 {
734 $resultTo = preg_replace("/(.*)\\<(.*)\\>/i", '$2', $resultTo);
735 }
736
737 return $resultTo;
738 }
739
745 public function getAdditionalParameters()
746 {
747 return $this->additionalParameters;
748 }
749
755 public function getContext()
756 {
757 return $this->context;
758 }
759
765 public function dump()
766 {
767 $result = '';
768 $delimeter = str_repeat('-',5);
769
770 $result .= $delimeter."TO".$delimeter."\n".$this->getTo()."\n\n";
771 $result .= $delimeter."SUBJECT".$delimeter."\n".$this->getSubject()."\n\n";
772 $result .= $delimeter."HEADERS".$delimeter."\n".$this->getHeaders()."\n\n";
773 $result .= $delimeter."BODY".$delimeter."\n".$this->getBody()."\n\n";
774 $result .= $delimeter."ADDITIONAL PARAMETERS".$delimeter."\n".$this->getAdditionalParameters()."\n\n";
775
776 return $result;
777 }
778
779
786 public static function is8Bit($inputString)
787 {
788 return preg_match("/[\\x80-\\xFF]/", $inputString) > 0;
789 }
790
798 public static function encodeMimeString($text, $charset)
799 {
800 if(!static::is8Bit($text))
801 return $text;
802
803 //$maxl = IntVal((76 - strlen($charset) + 7)*0.4);
804 $res = "";
805 $maxl = 40;
806 $eol = static::getMailEol();
807 $len = mb_strlen($text);
808 for($i=0; $i<$len; $i=$i+$maxl)
809 {
810 if($i>0)
811 $res .= $eol."\t";
812 $res .= "=?".$charset."?B?".base64_encode(mb_substr($text, $i, $maxl))."?=";
813 }
814 return $res;
815 }
816
824 public static function encodeSubject($text, $charset)
825 {
826 return "=?".$charset."?B?".base64_encode($text)."?=";
827 }
828
836 public static function encodeHeaderFrom($text, $charset)
837 {
838 $i = mb_strlen($text);
839 while($i > 0)
840 {
841 if(ord(mb_substr($text, $i - 1, 1))>>7)
842 break;
843 $i--;
844 }
845 if($i==0)
846 return $text;
847 else
848 return "=?".$charset."?B?".base64_encode(mb_substr($text, 0, $i))."?=".mb_substr($text, $i);
849 }
850
856 public static function getMailEol()
857 {
858 static $eol = false;
859 if($eol !== false)
860 {
861 return $eol;
862 }
863
864 if ((int)(explode('.', phpversion())[0]) >= 8)
865 {
866 $eol = "\r\n";
867 }
868 elseif(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
869 {
870 $eol = "\r\n";
871 }
872 elseif(strtoupper(substr(PHP_OS, 0, 3)) <> 'MAC')
873 {
874 $eol = "\n"; //unix
875 }
876 else
877 {
878 $eol = "\r";
879 }
880
881 return $eol;
882 }
883
884
890 protected function getReplacedImageCid($matches)
891 {
892 $src = $matches[3];
893
894 if($src == "")
895 {
896 return $matches[0];
897 }
898
899 if(array_key_exists($src, $this->filesReplacedFromBody))
900 {
901 $uid = $this->filesReplacedFromBody[$src]["ID"];
902 return $matches[1].$matches[2]."cid:".$uid.$matches[4].$matches[5];
903 }
904
905 $uri = new Uri($src);
906 $filePath = Application::getDocumentRoot() . $uri->getPath();
907 $io = \CBXVirtualIo::GetInstance();
908 $filePath = $io->GetPhysicalName($filePath);
909 if(!File::isFileExists($filePath))
910 {
911 return $matches[0];
912 }
913
914 foreach($this->attachment as $attachIndex => $attach)
915 {
916 if($filePath == $attach['PATH'])
917 {
918 $this->attachment[$attachIndex]['RELATED'] = true;
919 return $matches[1].$matches[2]."cid:".$attach['ID'].$matches[4].$matches[5];
920 }
921 }
922
923 if ($this->settingMaxFileSize > 0)
924 {
925 $fileIoObject = new File($filePath);
926 if ($fileIoObject->getSize() > $this->settingMaxFileSize)
927 {
928 return $matches[0];
929 }
930 }
931
932
933 $imageInfo = (new \Bitrix\Main\File\Image($filePath))->getInfo();
934 if (!$imageInfo)
935 {
936 return $matches[0];
937 }
938
939 if (function_exists("image_type_to_mime_type"))
940 {
941 $contentType = image_type_to_mime_type($imageInfo->getFormat());
942 }
943 else
944 {
945 $contentType = $this->imageTypeToMimeType($imageInfo->getFormat());
946 }
947
948 $uid = uniqid(md5($src));
949
950 $this->filesReplacedFromBody[$src] = array(
951 "RELATED" => true,
952 "SRC" => $src,
953 "PATH" => $filePath,
954 "CONTENT_TYPE" => $contentType,
955 "NAME" => bx_basename($src),
956 "ID" => $uid,
957 );
958
959 return $matches[1].$matches[2]."cid:".$uid.$matches[4].$matches[5];
960 }
961
966 protected function getReplacedImageSrc($matches)
967 {
968 $src = $matches[3];
969 if($src == "")
970 {
971 return $matches[0];
972 }
973
974 $srcTrimmed = trim($src);
975 if(mb_substr($srcTrimmed, 0, 2) == "//")
976 {
977 $src = $this->trackLinkProtocol . ":" . $srcTrimmed;
978 }
979 else if(mb_substr($srcTrimmed, 0, 1) == "/")
980 {
981 $srcModified = false;
982 if(!empty($this->attachment))
983 {
984 $io = \CBXVirtualIo::GetInstance();
985 $filePath = $io->GetPhysicalName(Application::getDocumentRoot().$srcTrimmed);
986 foreach($this->attachment as $attachIndex => $attach)
987 {
988 if($filePath == $attach['PATH'])
989 {
990 $this->attachment[$attachIndex]['RELATED'] = true;
991 $src = "cid:".$attach['ID'];
992 $srcModified = true;
993 break;
994 }
995 }
996 }
997
998 if(!$srcModified)
999 {
1000 $src = $this->trackLinkProtocol . "://".$this->settingServerName . $srcTrimmed;
1001 }
1002 }
1003
1004 $add = '';
1005 if (mb_stripos($matches[0], '<img') === 0 && !preg_match("/<img[^>]*?\\s+alt\\s*=[^>]+>/is", $matches[0]))
1006 {
1007 $add = ' alt="" ';
1008 }
1009
1010 return $matches[1] . $matches[2] . $src . $matches[4] . $add . $matches[5];
1011 }
1012
1020 public function replaceImages($text)
1021 {
1022 $replaceImageFunction = 'getReplacedImageSrc';
1023 if($this->settingAttachImages)
1024 $replaceImageFunction = 'getReplacedImageCid';
1025
1026 $this->filesReplacedFromBody = array();
1027 $textReplaced = preg_replace_callback(
1028 "/(<img\\s[^>]*?(?<=\\s)src\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1029 array($this, $replaceImageFunction),
1030 $text
1031 );
1032 if($textReplaced !== null) $text = $textReplaced;
1033
1034 $textReplaced = preg_replace_callback(
1035 "/(background\\s*:\\s*url\\s*\\(|background-image\\s*:\\s*url\\s*\\()([\"']?)(.*?)(\\2)(\\s*\\)(.*?);)/is",
1036 array($this, $replaceImageFunction),
1037 $text
1038 );
1039 if($textReplaced !== null) $text = $textReplaced;
1040
1041 $textReplaced = preg_replace_callback(
1042 "/(<td\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1043 array($this, $replaceImageFunction),
1044 $text
1045 );
1046 if($textReplaced !== null) $text = $textReplaced;
1047
1048 $textReplaced = preg_replace_callback(
1049 "/(<table\\s[^>]*?(?<=\\s)background\\s*=\\s*)([\"']?)(.*?)(\\2)(\\s.+?>|\\s*>)/is",
1050 array($this, $replaceImageFunction),
1051 $text
1052 );
1053 if($textReplaced !== null) $text = $textReplaced;
1054
1055 return $text;
1056 }
1057
1062 private function trackRead($html)
1063 {
1064 if(!$this->trackReadLink)
1065 {
1066 return $html;
1067 }
1068
1069 $url = $this->trackReadLink;
1070 if (mb_substr($url, 0, 4) !== 'http')
1071 {
1072 $url = $this->trackLinkProtocol . "://" . $this->settingServerName . $url;
1073 }
1074
1075 $html .= '<img src="' . $url . '" border="0" height="1" width="1" alt="" />';
1076
1077 return $html;
1078 }
1079
1087 public function replaceHrefs($text)
1088 {
1089 if($this->settingServerName != '')
1090 {
1091 $pattern = "/(<a\\s[^>]*?(?<=\\s)href\\s*=\\s*)([\"'])(\\/.*?|http:\\/\\/.*?|https:\\/\\/.*?)(\\2)(\\s.+?>|\\s*>)/is";
1092 $text = preg_replace_callback(
1093 $pattern,
1094 array($this, 'trackClick'),
1095 $text
1096 );
1097 }
1098
1099 return $text;
1100 }
1101
1109 public function trackClick($matches)
1110 {
1111 $href = $matches[3];
1112 if ($href == "")
1113 {
1114 return $matches[0];
1115 }
1116
1117 if(mb_substr($href, 0, 2) == '//')
1118 {
1119 $href = $this->trackLinkProtocol . ':' . $href;
1120 }
1121
1122 if(mb_substr($href, 0, 1) == '/')
1123 {
1124 $href = $this->trackLinkProtocol . '://' . $this->settingServerName . $href;
1125 }
1126
1127 if($this->trackClickLink)
1128 {
1129 if($this->trackClickUrlParams)
1130 {
1131 $hrefAddParam = '';
1132 foreach($this->trackClickUrlParams as $k => $v)
1133 $hrefAddParam .= '&'.htmlspecialcharsbx($k).'='.htmlspecialcharsbx($v);
1134
1135 $parsedHref = explode("#", $href);
1136 $parsedHref[0] .= (strpos($parsedHref[0], '?') === false? '?' : '&').mb_substr($hrefAddParam, 1);
1137 $href = implode("#", $parsedHref);
1138 }
1139
1140 $href = $this->trackClickLink . '&url=' . urlencode($href) . '&sign=' . urlencode(Tracking::getSign($href));
1141 if (!preg_match('/^http:\/\/|https:\/\//', $this->trackClickLink))
1142 {
1143 $href = $this->trackLinkProtocol . '://' . $this->settingServerName . $href;
1144 }
1145 }
1146
1147 return $matches[1].$matches[2].$href.$matches[4].$matches[5];
1148 }
1149
1154 protected function imageTypeToMimeType($type)
1155 {
1156 $types = array(
1157 1 => "image/gif",
1158 2 => "image/jpeg",
1159 3 => "image/png",
1160 4 => "application/x-shockwave-flash",
1161 5 => "image/psd",
1162 6 => "image/bmp",
1163 7 => "image/tiff",
1164 8 => "image/tiff",
1165 9 => "application/octet-stream",
1166 10 => "image/jp2",
1167 11 => "application/octet-stream",
1168 12 => "application/octet-stream",
1169 13 => "application/x-shockwave-flash",
1170 14 => "image/iff",
1171 15 => "image/vnd.wap.wbmp",
1172 16 => "image/xbm",
1173 );
1174 if(!empty($types[$type]))
1175 return $types[$type];
1176 else
1177 return "application/octet-stream";
1178 }
1179
1180 protected function addMessageIdToBody($body, $isHtml, $messageId)
1181 {
1182 if($this->settingMailAddMessageId && !empty($messageId))
1183 {
1184 $body .= $isHtml ? "<br><br>" : "\n\n";
1185 $body .= "MID #" . $messageId . "\r\n";
1186 }
1187
1188 return $body;
1189 }
1190
1197 protected function filterHeaderEmails(array &$headers)
1198 {
1199 if (!$this->useBlacklist || !Internal\BlacklistTable::hasBlacklistedEmails())
1200 {
1201 return;
1202 }
1203
1204 $list = [];
1205 $allEmails = [mb_strtolower($this->to)];
1206
1207 // get all emails for query Blacklist, prepare emails as Address instances
1208 foreach ($headers as $name => $value)
1209 {
1210 // exclude non target headers
1211 if (!in_array(mb_strtolower($name), static::$emailHeaders))
1212 {
1213 continue;
1214 }
1215
1216 $list[$name] = [];
1217 $emails = explode(',', $value);
1218 foreach ($emails as $email)
1219 {
1220 $email = trim($email);
1221 if (!$email)
1222 {
1223 continue;
1224 }
1225
1226 $address = new Address($email);
1227 $email = $address->getEmail();
1228 if ($email)
1229 {
1230 $list[$name][] = $address;
1231 $allEmails[] = $address->getEmail();
1232 }
1233 }
1234 }
1235
1236 // get blacklisted emails from all emails
1237 $allEmails = array_diff($allEmails, $this->blacklistCheckedEmails);
1238 if (!empty($allEmails))
1239 {
1240 $blacklisted = Internal\BlacklistTable::getList([
1241 'select' => ['CODE'],
1242 'filter' => ['=CODE' => $allEmails]
1243 ])->fetchAll();
1244 $blacklisted = array_column($blacklisted, 'CODE');
1245
1246 $this->blacklistedEmails = array_unique(array_merge($this->blacklistedEmails, $blacklisted));
1247 $this->blacklistCheckedEmails = array_merge($this->blacklistCheckedEmails, $allEmails);
1248 }
1249
1250 if (empty($this->blacklistedEmails))
1251 {
1252 return;
1253 }
1254
1255 // remove blacklisted emails, remove empty headers
1256 $blacklisted = $this->blacklistedEmails;
1257 foreach ($headers as $name => $value)
1258 {
1259 // exclude non target headers
1260 if (!in_array(mb_strtolower($name), static::$emailHeaders))
1261 {
1262 continue;
1263 }
1264 // filter Address instances by blacklist
1265 $emails = array_filter(
1266 $list[$name],
1267 function (Address $address) use ($blacklisted)
1268 {
1269 $email = $address->getEmail();
1270 return $email && !in_array($email, $blacklisted);
1271 }
1272 );
1273 // get emails from Address instances
1274 $emails = array_map(
1275 function (Address $address)
1276 {
1277 return $address->getName() ? $address->get() : $address->getEmail();
1278 },
1279 $emails
1280 );
1281 // get header emails as string
1282 $emails = implode(', ', $emails);
1283 // remove empty or update headers
1284 if (!$emails)
1285 {
1286 unset($headers[$name]);
1287 }
1288 else
1289 {
1290 $headers[$name] = $emails;
1291 }
1292 }
1293 }
1294
1300 public static function toPunycode($to)
1301 {
1302 $email = $to;
1303 $withComment = false;
1304
1305 if (preg_match("#.*?[<\\[(](.*?)[>\\])].*#i", $to, $matches) && $matches[1] <> '')
1306 {
1307 $email = $matches[1];
1308 $withComment = true;
1309 }
1310
1311 $parts = explode("@", $email);
1312 $domain = $parts[1];
1313
1314 $errors = [];
1315 $domain = \CBXPunycode::ToASCII($domain, $errors);
1316
1317 if (empty($errors))
1318 {
1319 $email = "{$parts[0]}@{$domain}";
1320
1321 if ($withComment)
1322 {
1323 $email = preg_replace("#(.*?)[<\\[(](.*?)[>\\])](.*)#i", '$1<'.$email.'>$3', $to);
1324 }
1325
1326 return $email;
1327 }
1328
1329 return $to;
1330 }
1331}
static htmlToText(string $body)
Definition converter.php:7
__construct(array $mailParams)
Definition mail.php:71
static encodeHeaderFrom($text, $charset)
Definition mail.php:836
getReplacedImageCid($matches)
Definition mail.php:890
setAdditionalParameters($additionalParameters='')
Definition mail.php:291
static send($mailParams)
Definition mail.php:162
static is8Bit($inputString)
Definition mail.php:786
addMessageIdToBody($body, $isHtml, $messageId)
Definition mail.php:1180
setBody($bodyPart)
Definition mail.php:303
imageTypeToMimeType($type)
Definition mail.php:1154
static getMailEol()
Definition mail.php:856
trackClick($matches)
Definition mail.php:1109
hasImageAttachment($checkRelated=false)
Definition mail.php:403
static encodeMimeString($text, $charset)
Definition mail.php:798
setSubject($subject)
Definition mail.php:566
$settingMailEncodeQuotedPrintable
Definition mail.php:28
filterHeaderEmails(array &$headers)
Definition mail.php:1197
static createInstance(array $mailParams)
Definition mail.php:151
setHeaders(array $headers)
Definition mail.php:554
getReplacedImageSrc($matches)
Definition mail.php:966
static $emailHeaders
Definition mail.php:64
static encodeSubject($text, $charset)
Definition mail.php:824
static toPunycode($to)
Definition mail.php:1300
$settingConvertNewLineUnixToWindows
Definition mail.php:22
static getLinkClick($moduleId, $fields, $urlPage=null)
Definition tracking.php:129
static getSign($value)
Definition tracking.php:185
static getLinkRead($moduleId, $fields, $urlPage=null)
Definition tracking.php:111