3namespace Bitrix\Mail\Helper;
5use Bitrix\Mail\Integration\Calendar\ICal\ICalMailManager;
6use Bitrix\Mail\Internals\MailMessageAttachmentTable;
7use Bitrix\Mail\Internals\MessageAccessTable;
8use Bitrix\Mail\Internals\MessageClosureTable;
9use Bitrix\Mail\MailboxTable;
10use Bitrix\Mail\MailMessageTable;
12use Bitrix\Main\Security;
13use Bitrix\Mail\Internals;
14use Bitrix\Mail\Helper\MessageAccess as AccessHelper;
15use Bitrix\Main\Config\Option;
16use Bitrix\Main\Config\Ini;
23 private const MAX_FILE_SIZE_MAIL_ATTACHMENT = 20000000;
30 return self::MAX_FILE_SIZE_MAIL_ATTACHMENT;
34 return (
int)Option::get(
'main',
'max_file_size');
40 return floor(static::getMaxAttachedFilesSize()/4)*3;
51 $validTagAttributes = [
67 $tableAttributes = array_merge(
75 $tdAttributes = array_merge(
86 'table' => $tableAttributes,
87 'center' => $validTagAttributes,
88 'div' => $validTagAttributes,
93 private static function prepareField($value)
99 if ($address->validate())
102 'name' => $address->getName(),
103 'email' => $address->getEmail(),
104 'formated' => ($address->getName() ? $address->get() : $address->getEmail()),
118 private static function checkMessageIsOutcomeByField(
$message,$keyField,
$value)
122 $isFromField = in_array($keyField, [
'__from',
'__reply_to']);
125 if ($address->validate())
127 if ($isFromField && $address->getEmail() ==
$message[
'__email'])
150 if ($address->validate())
152 $message[
'__email'] = $address->getEmail();
158 '__from' =>
'FIELD_FROM',
159 '__reply_to' =>
'FIELD_REPLY_TO',
160 '__to' =>
'FIELD_TO',
161 '__cc' =>
'FIELD_CC',
162 '__bcc' =>
'FIELD_BCC',
163 '__rcpt' =>
'FIELD_RCPT',
168 foreach ($fieldsMap as $field)
170 if (mb_strlen(
$message[$field]) == 255)
174 $message[
'FIELD_FROM'] = $parsedHeader->getHeader(
'FROM');
175 $message[
'FIELD_REPLY_TO'] = $parsedHeader->getHeader(
'REPLY-TO');
176 $message[
'FIELD_TO'] = $parsedHeader->getHeader(
'TO');
177 $message[
'FIELD_CC'] = $parsedHeader->getHeader(
'CC');
178 $message[
'FIELD_BCC'] = join(
', ', array_merge(
179 (
array) $parsedHeader->getHeader(
'X-Original-Rcpt-to'),
180 (
array) $parsedHeader->getHeader(
'BCC')
188 foreach ($fieldsMap as $fieldKey => $fieldName)
192 if($fieldName ===
'FIELD_FROM')
194 if(static::checkMessageIsOutcomeByField(
$message,$fieldKey,
$message[$fieldName]))
199 $filed = static::prepareField(
$message[$fieldName]);
209 foreach (explode(
',',
$message[$fieldName]) as $item)
211 if(static::checkMessageIsOutcomeByField(
$message,$fieldKey,$item))
216 $filed = static::prepareField($item);
237 $urlParams =
array();
239 if (isset(
$_REQUEST[
'mail_uf_message_token']) && is_string(
$_REQUEST[
'mail_uf_message_token']))
241 $urlParams[
'mail_uf_message_token'] =
$_REQUEST[
'mail_uf_message_token'];
246 if ($diskFile =
Attachment\Storage::getObjectByAttachment($item,
true))
249 'id' => sprintf(
'n%u', $diskFile->getId()),
250 'name' => $item[
'FILE_NAME'],
251 'url' => $urlManager->getUrlForShowFile($diskFile, $urlParams),
252 'size' => \CFile::formatSize($diskFile->getSize()),
253 'fileId' => $diskFile->getFileId(),
254 'objectId' => $diskFile->getId(),
255 'bytes' => $diskFile->getSize(),
258 if (\
Bitrix\Disk\TypeFile::isImage($diskFile))
260 $message[
'__files'][
$k][
'preview'] = $urlManager->getUrlForShowFile(
263 array(
'width' => 80,
'height' => 80,
'exact' =>
'Y'),
269 $inlineParams = array_merge(
270 array(
'__bxacid' => sprintf(
'n%u', $diskFile->getId())),
273 $message[
'BODY_HTML'] = preg_replace(
274 sprintf(
'/("|\')\s*aid:%u\s*\1/i', $item[
'ID']),
275 sprintf(
'\1%s\1', $urlManager->getUrlForShowFile($diskFile, $inlineParams)),
281 $file = \CFile::getFileArray($item[
'FILE_ID']);
282 if (!empty($file) && is_array($file))
286 'name' => $item[
'FILE_NAME'],
287 'url' => $file[
'SRC'],
288 'size' => \CFile::formatSize($file[
'FILE_SIZE']),
289 'fileId' => $file[
'ID'],
290 'bytes' => $file[
'FILE_SIZE'],
293 if (\CFile::isImage($item[
'FILE_NAME'], $item[
'CONTENT_TYPE']))
295 $preview = \CFile::resizeImageGet(
296 $file,
array(
'width' => 80,
'height' => 80),
300 if (!empty($preview[
'src']))
302 $message[
'__files'][
$k][
'preview'] = $preview[
'src'];
306 $message[
'BODY_HTML'] = preg_replace(
307 sprintf(
'/("|\')\s*aid:%u\s*\1/i', $item[
'ID']),
308 sprintf(
'\1%s\1', $file[
'SRC']),
342 $access = $messageAccess->isOwner();
349 $token = $signature =
'';
350 if (is_string(
$_REQUEST[
'mail_uf_message_token']) && mb_strpos(
$_REQUEST[
'mail_uf_message_token'],
':') > 0)
352 [$token, $signature] = explode(
':',
$_REQUEST[
'mail_uf_message_token'], 2);
355 if ($token <>
'' && $signature <>
'')
357 $excerpt = MessageAccessTable::getList(
array(
358 'select' =>
array(
'SECRET',
'MESSAGE_ID',
'ENTITY_TYPE',
'ENTITY_ID'),
361 '=MAILBOX_ID' =>
$message[
'MAILBOX_ID'],
366 if (!empty($excerpt[
'SECRET']))
368 if (self::checkAccessForEntityToken($excerpt[
'ENTITY_TYPE'], (
int)$excerpt[
'ENTITY_ID'],
$userId))
370 $salt = self::getSaltByEntityType($excerpt[
'ENTITY_TYPE'], (
int)$excerpt[
'ENTITY_ID'],
$userId);
374 if ($signer->validate($excerpt[
'SECRET'], $signature, $salt))
381 'select' =>
array(
'PARENT_ID'),
384 '=PARENT_ID' => $excerpt[
'MESSAGE_ID'],
397 $access = $messageAccess->canViewMessage();
400 if (
false ===
$message[
'__access_level'])
411 return str_rot13(join(
420 self::isolateBase64Files((
string)
$fields[
'BODY']),
427 return str_rot13($string);
437 static $countersForUsers;
439 if (empty($countersForUsers))
441 $countersForUsers = [];
444 if (!isset($countersForUsers[
$userId]))
448 if (empty($mailboxes))
453 $mailboxIds = array_column($mailboxes,
'ID');
465 '=ENTITY_TYPE' =>
'MAILBOX',
466 '@ENTITY_ID' => $mailboxIds,
474 foreach ($totalUnseen as $index => $item)
476 $totalCounter += $item[
'VALUE'];
479 'UNSEEN' => $item[
'VALUE'],
485 'totalCounter' => $totalCounter,
489 if($onlyGeneralCounter)
491 return $countersForUsers[
$userId][
'totalCounter'];
495 return $countersForUsers[
$userId][
'mailboxesWithCounters'];
501 if (
$message[
'ATTACHMENTS'] > 0 || !(
$message[
'OPTIONS'][
'attachments'] > 0))
511 $mailboxHelper = Mailbox::createInstance(
$message[
'MAILBOX_ID'],
false);
513 $attachments = empty($mailboxHelper) ?
false : $mailboxHelper->downloadAttachments(
$message);
515 if (
false === $attachments)
518 'Helper\Message: Attachments downloading failed (%u:%s:%u)',
524 if (!empty($mailboxHelper) && !$mailboxHelper->getErrors()->isEmpty())
526 $logEntry .= PHP_EOL . join(PHP_EOL, $mailboxHelper->getErrors()->toArray());
529 addMessage2Log($logEntry,
'mail', 2);
534 $originalBody =
$message[
'BODY_HTML'] ??
null;
535 foreach ($attachments as
$i => $item)
537 $attachFields =
array(
539 'FILE_NAME' => $item[
'FILENAME'],
540 'CONTENT_TYPE' => $item[
'CONTENT-TYPE'],
541 'FILE_DATA' => $item[
'BODY'],
542 'CONTENT_ID' => $item[
'CONTENT-ID'],
547 if ($attachmentId > 0)
553 $bodyWithReplaced = self::replaceBodyInlineImgContentId(
555 (
string)$item[
'CONTENT-ID'],
558 if ($bodyWithReplaced)
560 $message[
'BODY_HTML'] = $bodyWithReplaced;
568 if ($originalBody !==
$message[
'BODY_HTML'])
579 $column = trim($column);
582 $columns = preg_split(
"/>,?/", $column);
585 foreach ($columns as $value)
589 if(preg_match(
"/</", $value))
596 $validColumns[] = $value;
600 return $validColumns;
607 $prefix =
'mail-message-';
608 $wrapper =
'#mail-message-wrapper ';
609 if(substr($head,0,1)===
'@') $wrapper =
'';
611 $head = preg_replace(
'%([\.#])([a-z][-_a-z0-9]+)%mi',
'$1'.$prefix.
'$2', $head);
613 return $wrapper.preg_replace(
'/,/',
', ' . $wrapper .
' ', $head).$body.$closure;
618 $wrapper =
'#mail-message-wrapper ';
619 $openingTag =
$matches[
'openingTag'];
620 $closingTag =
$matches[
'closingTag'];
622 $bodySelectorPattern =
'#(.*?)(^|\s)(body)\s*((?:\{)(?:.*?)(?:\}))(.*)#msi';
623 $bodySelector = preg_replace($bodySelectorPattern,
'$2'.$wrapper.
'$4', $styles);
625 $styles = preg_replace($bodySelectorPattern,
'$1$5', $styles);
626 $styles = preg_replace(
'#(^|\s)(body)\s*({)#iU',
'$1mail-msg-view-body$3', $styles);
627 $styles = preg_replace_callback(
'%(?:^|\s)(?<head>[@#\.]?[a-z].*?\{)(?<body>.*?)(?<closure>\})%msi',
'static::isolateSelector', $styles);
628 return $openingTag.$bodySelector.$styles.$closingTag;
633 $prefix =
'mail-message-';
634 $html = preg_replace(
'%((?:^|\s)(?:class|id)(?:^|\s*)(?:=)(?:^|\s*)(\"|\'))((?:.*?)(?:\2))%',
'$1'.$prefix.
'$3', $html);
640 Ini::adjustPcreBacktrackLimit(strlen($messageHtml)*2);
643 $messageHtml = preg_replace(
'%((?:^|\s)position(?:^|\s)?:(?:^|\s)?)(absolute|fixed|inherit)%',
'$1relative', $messageHtml);
646 $messageHtmlAfterClearingFromMedia = preg_replace(
'%@media\b[^{]*({((?:[^{}]+|(?1))*)})%mi',
'', $messageHtml);
652 if (is_null($messageHtmlAfterClearingFromMedia))
654 $messageHtml = preg_replace(
'/@media/i',
'@mail-message-disabled-media', $messageHtml);
658 $messageHtml = $messageHtmlAfterClearingFromMedia;
662 $messageHtml = preg_replace(
'%@font-face\b[^{]*({(?>[^{}]++|(?1))*})%mi',
'', $messageHtml);
664 $messageHtml = static::isolateStylesInTheBody($messageHtml);
666 return preg_replace_callback(
'|(?<openingTag><style[^>]*>)(?<styles>.*)(?<closingTag><\/style>)|isU',
'static::isolateStylesInTheTag', $messageHtml);
671 $cleared = preg_replace(
'/<!--.*?-->/is',
'', $html);
672 $cleared = preg_replace(
'/<script[^>]*>.*?<\/script>/is',
'', $cleared);
673 $cleared = preg_replace(
'/<title[^>]*>.*?<\/title>/is',
'', $cleared);
674 $sanitizer = new \CBXSanitizer();
676 $sanitizer->applyDoubleEncode(
false);
677 $sanitizer->addTags(static::getWhitelistTagAttributes());
678 $cleared = $sanitizer->sanitizeHtml($cleared);
682 $cleared = static::isolateMessageStyles($cleared);
690 if (empty($messageIds) || !is_array($messageIds))
695 $messages = \Bitrix\Mail\MailMessageUidTable::getList([
700 '=MAILBOX_ID' => $mailboxId,
701 '@MESSAGE_ID' => $messageIds,
705 $notProcessed = array_combine($messageIds, $messageIds);
707 $mailboxHelper = Mailbox::createInstance($mailboxId,
false);
709 if(!empty($mailboxHelper))
711 $mailbox = $mailboxHelper->getMailbox();
715 $technicalTitle = $mailboxHelper->downloadMessage(
$message);
718 $charset = $mailbox[
'CHARSET'] ?: $mailbox[
'LANG_CHARSET'];
726 if (rtrim(
$text) || $html)
728 \CMailMessage::update(
731 'BODY' => rtrim(
$text),
732 'BODY_HTML' => $html,
738 self::updateMailEntityOptionsRow($mailboxId, (
int)
$message[
'MESSAGE_ID']);
739 unset($notProcessed[
$message[
'MESSAGE_ID']]);
745 self::updateMailEntityOptionsRow($mailboxId, (
int)
$messageId);
755 case Message::ENTITY_TYPE_IM_CHAT:
757 case Message::ENTITY_TYPE_CALENDAR_EVENT:
761 return sprintf(
'user'.
'%u',
$userId);
770 private static function checkAccessForEntityToken(?
string $entityType,
int $entityId,
int $userId): bool
774 case Message::ENTITY_TYPE_IM_CHAT:
776 case Message::ENTITY_TYPE_CALENDAR_EVENT:
791 'MAILBOX_ID' => $mailboxId,
793 'ENTITY_TYPE' =>
'MESSAGE',
794 'PROPERTY_NAME' =>
'UNSYNC_BODY',
811 return preg_match(
'/<img([^>]+)src\s*=\s*([\'"])?\s*((?:http:\/\/)?cid:.+)\s*\2([^>]*)>/is', $body);
825 Ini::adjustPcreBacktrackLimit(strlen($body)*2);
827 $replacedBody = preg_replace(
828 sprintf(
'/<img([^>]+)src\s*=\s*(\'|\")?\s*((?:http:\/\/)?cid:%s)\s*\2([^>]*)>/is', preg_quote(
$contentId,
'/')),
829 sprintf(
'<img\1src="aid:%u"\4>', $attachmentId),
833 return $replacedBody ?? $body;
838 $pattern =
'/\[\s*data:(?!text\b)[^;]+;base64,\S+ \]/';
845 $attachments = MailMessageAttachmentTable::getList([
851 'CONTENT-TYPE' =>
'CONTENT_TYPE',
855 '@CONTENT_TYPE' => ICalMailManager::CONTENT_TYPES
859 return ICalMailManager::hasICalAttachments($attachments);
if(! $messageFields||!isset($messageFields['message_id'])||!isset($messageFields['status'])||!CModule::IncludeModule("messageservice")) $messageId
if(!is_object($USER)||! $USER->IsAuthorized()) $userId
static checkAccessForChat(int $chatId, int $userId)
static checkAccessForCalendarEvent(int $calendarEventId, int $userId)
static updateMailEntityOptionsRow($mailboxId, $messageId)
static sanitizeHtml($html, $isolateStyles=false)
static isolateMessageStyles($messageHtml)
static isolateBase64Files(string $text)
static prepareSearchString($string)
static getMaxAttachedFilesSizeAfterEncoding()
static isMailboxOwner(int $mailboxId, int $userId)
static getSaltByEntityType(string $entityType, int $entityId, ?int $userId=null)
static prepare(&$message)
const ENTITY_TYPE_CALENDAR_EVENT
static isIcalMessage(\Bitrix\Mail\Item\Message $message)
static prepareSearchContent(&$fields)
static parseAddressList($column)
static isolateStylesInTheBody($html)
static ensureAttachments(&$message)
static isolateStylesInTheTag($matches)
static isBodyNeedUpdateAfterLoadAttachments(string $body)
static getMaxAttachedFilesSize()
static reSyncBody($mailboxId, $messageIds)
static replaceBodyInlineImgContentId(string $body, string $contentId, int $attachmentId)
static getCountersForUserMailboxes($userId, $onlyGeneralCounter=false)
const ENTITY_TYPE_IM_CHAT
static hasAccess(&$message, $userId=null)
static getWhitelistTagAttributes()
static isolateSelector($matches)
const FIELD_SANITIZE_ON_VIEW
static getUserMailbox($mailboxId, $userId=null)
static getUserMailboxes($userId=null, bool $onlyIds=false)
static createByMessageId(int $messageId, int $userId)
static getList(array $parameters=array())
static update($primary, array $data)
static parseHeader($header, $charset)
static isLongMessageBody(?string &$messageBody)
static addAttachment($arFields)
static prepareLongMessage(&$messageBody, &$messageBodyHtml)
static parseMessage($message, $charset)
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
const B_MAIL_SAVE_ATTACHMENTS
const BX_RESIZE_IMAGE_EXACT
if(!is_array($deviceNotifyCodes)) $access
IsModuleInstalled($module_id)
if(!Loader::includeModule('sale')) $pattern