Current Path : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/a537b/ |
Current File : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/a537b/helper.tar |
contactsstepper.php 0000664 00000003417 14774470223 0010517 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Mail; use Bitrix\Mail\Internals\MailContactTable; class ContactsStepper extends Main\Update\Stepper { protected static $moduleId = 'mail'; public function execute(array &$option) { $option['steps'] = Mail\MailMessageTable::getCount(array( '<=ID' => $option['lastId'] > 0 ? $option['lastId'] : 0, )); $option['count'] = Mail\MailMessageTable::getCount(); if ($option['steps'] >= $option['count']) { return false; } $res = Mail\MailMessageTable::getList(array( 'select' => array( 'ID', 'FIELD_FROM', 'FIELD_REPLY_TO', 'FIELD_TO', 'FIELD_CC', 'FIELD_BCC', 'MAILBOX_USER_ID' => 'MAILBOX.USER_ID', ), 'filter' => array( '>ID' => $option['lastId'] > 0 ? $option['lastId'] : 0, ), 'order' => array('ID' => 'ASC'), 'limit' => 1000, )); $contacts = array(); while ($item = $res->fetch()) { $option['steps']++; $option['lastId'] = $item['ID']; @array_push( $contacts, ...MailContact::getContactsData($item['FIELD_FROM'], $item['MAILBOX_USER_ID'], MailContactTable::ADDED_TYPE_FROM), ...MailContact::getContactsData($item['FIELD_REPLY_TO'], $item['MAILBOX_USER_ID'], MailContactTable::ADDED_TYPE_REPLY_TO), ...MailContact::getContactsData($item['FIELD_TO'], $item['MAILBOX_USER_ID'], MailContactTable::ADDED_TYPE_TO), ...MailContact::getContactsData($item['FIELD_CC'], $item['MAILBOX_USER_ID'], MailContactTable::ADDED_TYPE_CC), ...MailContact::getContactsData($item['FIELD_BCC'], $item['MAILBOX_USER_ID'], MailContactTable::ADDED_TYPE_BCC) ); if (count($contacts) >= 100) { MailContactTable::addContactsBatch($contacts); $contacts = array(); } } MailContactTable::addContactsBatch($contacts); return true; } } message.php 0000664 00000025114 14774470223 0006720 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Main\Security; use Bitrix\Mail; class Message { public static function prepare(&$message) { $message['__email'] = null; foreach (array($message['MAILBOX_EMAIL'], $message['MAILBOX_NAME'], $message['MAILBOX_LOGIN']) as $item) { $address = new Main\Mail\Address($item); if ($address->validate()) { $message['__email'] = $address->getEmail(); break; } } $fieldsMap = array( '__from' => 'FIELD_FROM', '__reply_to' => 'FIELD_REPLY_TO', '__to' => 'FIELD_TO', '__cc' => 'FIELD_CC', '__bcc' => 'FIELD_BCC', '__rcpt' => 'FIELD_RCPT', ); if ('' != $message['HEADER']) { foreach ($fieldsMap as $field) { if (strlen($message[$field]) == 255) { $parsedHeader = \CMailMessage::parseHeader($message['HEADER'], LANG_CHARSET); $message['FIELD_FROM'] = $parsedHeader->getHeader('FROM'); $message['FIELD_REPLY_TO'] = $parsedHeader->getHeader('REPLY-TO'); $message['FIELD_TO'] = $parsedHeader->getHeader('TO'); $message['FIELD_CC'] = $parsedHeader->getHeader('CC'); $message['FIELD_BCC'] = join(', ', array_merge( (array) $parsedHeader->getHeader('X-Original-Rcpt-to'), (array) $parsedHeader->getHeader('BCC') )); break; } } } foreach ($fieldsMap as $extField => $field) { $isFromField = in_array($extField, array('__from', '__reply_to')); $message[$extField] = array(); foreach (explode(',', $message[$field]) as $item) { if (trim($item)) { $address = new Main\Mail\Address($item); if ($address->validate()) { if ($isFromField && $address->getEmail() == $message['__email']) { $message['__is_outcome'] = true; } $message[$extField][] = array( 'name' => $address->getName(), 'email' => $address->getEmail(), 'formated' => ($address->getName() ? $address->get() : $address->getEmail()), ); } else { $message[$extField][] = array( 'name' => $item, ); } } } } if (empty($message['__reply_to'])) { $message['__reply_to'] = $message['__from']; } // @TODO: path $message['__href'] = sprintf('/mail/message/%u', $message['ID']); $urlManager = Attachment\Storage::getUrlManager(); if (!empty($message['__files']) && is_array($message['__files'])) { $urlParams = array(); if (isset($_REQUEST['mail_uf_message_token']) && is_string($_REQUEST['mail_uf_message_token'])) { $urlParams['mail_uf_message_token'] = $_REQUEST['mail_uf_message_token']; } foreach ($message['__files'] as $k => $item) { if ($diskFile = Attachment\Storage::getObjectByAttachment($item, true)) { $message['__files'][$k] = array( 'id' => sprintf('n%u', $diskFile->getId()), 'name' => $item['FILE_NAME'], 'url' => $urlManager->getUrlForShowFile($diskFile, $urlParams), 'size' => \CFile::formatSize($diskFile->getSize()), 'fileId' => $diskFile->getFileId(), 'objectId' => $diskFile->getId(), 'bytes' => $diskFile->getSize(), ); if (\Bitrix\Disk\TypeFile::isImage($diskFile)) { $message['__files'][$k]['preview'] = $urlManager->getUrlForShowFile( $diskFile, array_merge( array('width' => 80, 'height' => 80, 'exact' => 'Y'), $urlParams ) ); } $inlineParams = array_merge( array('__bxacid' => sprintf('n%u', $diskFile->getId())), $urlParams ); $message['BODY_HTML'] = preg_replace( sprintf('/("|\')\s*aid:%u\s*\1/i', $item['ID']), sprintf('\1%s\1', $urlManager->getUrlForShowFile($diskFile, $inlineParams)), $message['BODY_HTML'] ); } else { $file = \CFile::getFileArray($item['FILE_ID']); if (!empty($file) && is_array($file)) { $message['__files'][$k] = array( 'id' => $file['ID'], 'name' => $item['FILE_NAME'], 'url' => $file['SRC'], 'size' => \CFile::formatSize($file['FILE_SIZE']), 'fileId' => $file['ID'], 'bytes' => $file['FILE_SIZE'], ); if (\CFile::isImage($item['FILE_NAME'], $item['CONTENT_TYPE'])) { $preview = \CFile::resizeImageGet( $file, array('width' => 80, 'height' => 80), BX_RESIZE_IMAGE_EXACT, false ); if (!empty($preview['src'])) { $message['__files'][$k]['preview'] = $preview['src']; } } $message['BODY_HTML'] = preg_replace( sprintf('/("|\')\s*aid:%u\s*\1/i', $item['ID']), sprintf('\1%s\1', $file['SRC']), $message['BODY_HTML'] ); } else { unset($message['__files'][$k]); } } } } return $message; } public static function hasAccess(&$message, $userId = null) { global $USER; if (!($userId > 0 || is_object($USER) && $USER->isAuthorized())) { return false; } if (!($userId > 0)) { $userId = $USER->getId(); } $access = (bool) Mail\MailboxTable::getUserMailbox($message['MAILBOX_ID'], $userId); $message['__access_level'] = $access ? 'full' : false; if (!$access && isset($_REQUEST['mail_uf_message_token'])) { $token = $signature = ''; if (is_string($_REQUEST['mail_uf_message_token']) && strpos($_REQUEST['mail_uf_message_token'], ':') > 0) { list($token, $signature) = explode(':', $_REQUEST['mail_uf_message_token'], 2); } if (strlen($token) > 0 && strlen($signature) > 0) { $excerpt = Mail\Internals\MessageAccessTable::getList(array( 'select' => array('SECRET', 'MESSAGE_ID'), 'filter' => array( '=TOKEN' => $token, '=MAILBOX_ID' => $message['MAILBOX_ID'], ), 'limit' => 1, ))->fetch(); if (!empty($excerpt['SECRET'])) { $signer = new Security\Sign\Signer(new Security\Sign\HmacAlgorithm('md5')); if ($signer->validate($excerpt['SECRET'], $signature, sprintf('user%u', $userId))) { $access = $message['ID'] == $excerpt['MESSAGE_ID']; if (!$access) // check parent access { $access = (bool) Mail\Internals\MessageClosureTable::getList(array( 'select' => array('PARENT_ID'), 'filter' => array( '=MESSAGE_ID' => $message['ID'], '=PARENT_ID' => $excerpt['MESSAGE_ID'], ), ))->fetch(); } } } } } if (false === $message['__access_level']) { $message['__access_level'] = $access ? 'read' : false; } return $access; } public static function prepareSearchContent(&$fields) { // @TODO: filter short words, filter duplicates, str_rot13? return str_rot13(join( ' ', array( $fields['FIELD_FROM'], $fields['FIELD_REPLY_TO'], $fields['FIELD_TO'], $fields['FIELD_CC'], $fields['FIELD_BCC'], $fields['SUBJECT'], $fields['BODY'], ) )); } public static function prepareSearchString($string) { return str_rot13($string); } public static function getTotalUnseenCount($userId) { $unseenTotal = static::getTotalUnseenForMailboxes($userId); $unseen = 0; foreach ($unseenTotal as $index => $item) { $unseen += (int)$item['UNSEEN']; } return $unseen; } public static function getTotalUnseenForMailboxes($userId) { $mailboxes = Mail\MailboxTable::getUserMailboxes($userId); if (empty($mailboxes)) { return array(); } $mailboxes = array_combine( array_column($mailboxes, 'ID'), $mailboxes ); $mailboxFilter = array( 'LOGIC' => 'OR', ); foreach ($mailboxes as $item) { $mailboxFilter[] = array( '=MAILBOX_ID' => $item['ID'], '!@DIR_MD5' => array_map( 'md5', array_merge( (array) $item['OPTIONS']['imap'][MessageFolder::TRASH], (array) $item['OPTIONS']['imap'][MessageFolder::SPAM] ) ), ); } $totalUnseen = Mail\MailMessageUidTable::getList(array( 'select' => array( 'MAILBOX_ID', new \Bitrix\Main\Entity\ExpressionField('TOTAL', 'COUNT(1)'), new \Bitrix\Main\Entity\ExpressionField( 'UNSEEN', "COUNT(IF(%s IN('N','U'), 1, NULL))", array('IS_SEEN') ), ), 'filter' => array( $mailboxFilter, '>MESSAGE_ID' => 0, ), 'group' => array( 'MAILBOX_ID', ), ))->fetchAll(); $result = []; foreach ($totalUnseen as $index => $item) { $result[$item['MAILBOX_ID']] = [ 'TOTAL' => $item['TOTAL'], 'UNSEEN' => $item['UNSEEN'], ]; } return $result; } public static function ensureAttachments(&$message) { if ($message['ATTACHMENTS'] > 0 || !($message['OPTIONS']['attachments'] > 0)) { return; } if (Main\Config\Option::get('mail', 'save_attachments', B_MAIL_SAVE_ATTACHMENTS) !== 'Y') { return; } $mailboxHelper = Mailbox::createInstance($message['MAILBOX_ID'], false); $attachments = empty($mailboxHelper) ? false : $mailboxHelper->downloadAttachments($message); if (false === $attachments) { $logEntry = sprintf( 'Helper\Message: Attachments downloading failed (%u:%s:%u)', $message['MAILBOX_ID'], $message['DIR_MD5'], $message['MSG_UID'] ); if (!empty($mailboxHelper) && !$mailboxHelper->getErrors()->isEmpty()) { $logEntry .= PHP_EOL . join(PHP_EOL, $mailboxHelper->getErrors()->toArray()); } addMessage2Log($logEntry, 'mail', 2); return false; } foreach ($attachments as $i => $item) { $attachFields = array( 'MESSAGE_ID' => $message['ID'], 'FILE_NAME' => $item['FILENAME'], 'CONTENT_TYPE' => $item['CONTENT-TYPE'], 'FILE_DATA' => $item['BODY'], 'CONTENT_ID' => $item['CONTENT-ID'], ); $attachmentId = \CMailMessage::addAttachment($attachFields); if ($attachmentId > 0) { $message['ATTACHMENTS']++; $message['BODY_HTML'] = preg_replace( sprintf( '/<img([^>]+)src\s*=\s*(\'|\")?\s*(http:\/\/cid:%s)\s*\2([^>]*)>/is', preg_quote($item['CONTENT-ID'], '/') ), sprintf('<img\1src="aid:%u"\4>', $attachmentId), $message['BODY_HTML'] ); } } if ($message['ATTACHMENTS'] > 0) { \CMailMessage::update($message['ID'], array('BODY_HTML' => $message['BODY_HTML'])); return $message['ID']; } } public static function resync(&$message) { $mailboxHelper = Mailbox::createInstance($message['MAILBOX_ID'], false); $result = empty($mailboxHelper) ? false : $mailboxHelper->resyncMessage($message); if (false === $result) { $logEntry = sprintf( 'Helper\Message: Message resync failed (%u:%s:%u)', $message['MAILBOX_ID'], $message['DIR_MD5'], $message['MSG_UID'] ); if (!empty($mailboxHelper) && !$mailboxHelper->getErrors()->isEmpty()) { $logEntry .= PHP_EOL . join(PHP_EOL, $mailboxHelper->getErrors()->toArray()); } addMessage2Log($logEntry, 'mail', 3); } return $result; } } oauth/google.php 0000664 00000002353 14774470223 0007670 0 ustar 00 <?php namespace Bitrix\Mail\Helper\OAuth; use Bitrix\Main; use Bitrix\Mail; class Google extends Mail\Helper\OAuth { protected function __construct() { $this->oauthEntity = new GoogleInterface; $this->oauthEntity->addScope(array( 'email', 'https://mail.google.com/', )); } protected function check() { $provider = new \CSocServGoogleOAuth; return $provider->checkSettings(); } protected function mapUserData(array $userData) { return array( 'email' => $userData['email'], 'first_name' => $userData['given_name'], 'last_name' => $userData['family_name'], 'full_name' => $userData['name'], 'image' => $userData['picture'], 'error' => $userData['error']['message'], ); } public static function getServiceName() { return 'google'; } public function getControllerUrl() { return \CSocServAuth::getControllerUrl(); } } if (Main\Loader::includeModule('socialservices')) { class GoogleInterface extends \CGoogleOAuthInterface { public function getStorageTokens() { return false; } public function getTokenData() { return array( 'access_token' => $this->access_token, 'refresh_token' => $this->refresh_token, 'expires_in' => $this->accessTokenExpires, ); } } } oauth/yandex.php 0000664 00000002533 14774470223 0007704 0 ustar 00 <?php namespace Bitrix\Mail\Helper\OAuth; use Bitrix\Main; use Bitrix\Mail; class Yandex extends Mail\Helper\OAuth { protected function __construct() { $this->oauthEntity = new YandexInterface; } protected function check() { $provider = new \CSocServYandexAuth; return $provider->checkSettings(); } protected function mapUserData(array $userData) { return array( 'email' => $userData['default_email'], 'first_name' => $userData['first_name'], 'last_name' => $userData['last_name'], 'full_name' => $userData['real_name'], 'image' => sprintf('https://avatars.yandex.net/get-yapic/%s/islands-middle', $userData['default_avatar_id']), //'error' => $data['error']['message'], ); } public static function getServiceName() { return 'yandex'; } public function getControllerUrl() { return \CSocServYandexAuth::CONTROLLER_URL; } } if (Main\Loader::includeModule('socialservices')) { class YandexInterface extends \CYandexOAuthInterface { public function getStorageTokens() { return false; } public function getTokenData() { return array( 'access_token' => $this->access_token, 'refresh_token' => $this->refresh_token, 'expires_in' => $this->accessTokenExpires, ); } public function getNewAccessToken($refreshToken = false, $userId = 0, $save = false) { return false; } } } oauth/liveid.php 0000664 00000004127 14774470223 0007671 0 ustar 00 <?php namespace Bitrix\Mail\Helper\OAuth; use Bitrix\Main; use Bitrix\Mail; class LiveId extends Mail\Helper\OAuth { protected function __construct() { $this->oauthEntity = new LiveIdInterface; $this->oauthEntity->addScope(array( 'wl.emails', 'wl.imap', 'wl.offline_access', )); } protected function check() { $provider = new \CSocServLiveIdOAuth; return $provider->checkSettings(); } protected function mapUserData(array $userData) { return array( 'email' => $userData['emails']['account'], 'first_name' => $userData['first_name'], 'last_name' => $userData['last_name'], 'full_name' => $userData['name'], 'image' => sprintf('https://apis.live.net/v5.0/%s/picture?type=small', $userData['id']), 'error' => $userData['error']['message'], ); } public static function getServiceName() { return 'liveid'; } public function getControllerUrl() { return \CSocServLiveIdOAuth::CONTROLLER_URL; } } if (Main\Loader::includeModule('socialservices')) { class_exists('CSocServLiveIdOAuth'); class LiveIdInterface extends \CLiveIdOAuthInterface { public function setCode($code) { $this->code = $code; } public function setToken($access_token) { $this->access_token = $access_token; } public function getStorageTokens() { return false; } public function getTokenData() { return array( 'access_token' => $this->access_token, 'refresh_token' => $this->refresh_token, 'expires_in' => time() + $this->accessTokenExpires, ); } public function getCurrentUser() { if (empty($this->access_token)) { return false; } $httpClient = new \Bitrix\Main\Web\HttpClient(); $httpClient->setHeader('Authorization', 'Bearer ' . $this->access_token); $result = $httpClient->get(static::CONTACTS_URL); if (!empty($result)) { try { $result = \Bitrix\Main\Web\Json::decode($result); } catch (Exception $e) { $result = null; } } if (is_array($result)) { $result = array_merge( $result, $this->getTokenData() ); } return $result; } } } oauth/mailru.php 0000664 00000003077 14774470223 0007711 0 ustar 00 <?php namespace Bitrix\Mail\Helper\OAuth; use Bitrix\Main; use Bitrix\Mail; class Mailru extends Mail\Helper\OAuth { protected function __construct() { $this->oauthEntity = new MailruInterface( \CSocServMailRu2::getOption('mailru2_client_id'), \CSocServMailRu2::getOption('mailru2_client_secret') ); $this->oauthEntity->addScope(array( 'userinfo', 'mail.imap', )); } protected function check() { $provider = new \CSocServMailRu2; return $provider->checkSettings(); } protected function mapUserData(array $userData) { return array( 'email' => $userData['email'], 'first_name' => $userData['first_name'], 'last_name' => $userData['last_name'], 'full_name' => $userData['name'], 'image' => $userData['image'], 'error' => $userData['error_description'], ); } public static function getServiceName() { return 'mailru'; } public function getControllerUrl() { return \CSocServMailRu2::CONTROLLER_URL; } } if (Main\Loader::includeModule('socialservices')) { class_exists('CSocServMailRu2'); class MailruInterface extends \CMailRu2Interface { public function getStorageTokens() { return false; } public function getTokenData() { return array( 'access_token' => $this->access_token, 'refresh_token' => $this->refresh_token, 'expires_in' => $this->accessTokenExpires, ); } public function getCurrentUser() { $result = parent::getCurrentUser(); if (is_array($result)) { $result = array_merge( $result, $this->getTokenData() ); } return $result; } } } mailbox/mailboxsyncmanager.php 0000644 00000010177 14774470223 0012613 0 ustar 00 <?php namespace Bitrix\Mail\Helper\Mailbox; use COption; use CUserOptions; class MailboxSyncManager { private $userId; private $mailCheckInterval; private $syncOptionCategory = 'global'; private $syncOptionName = 'user_mailboxes_sync_info'; public function __construct($userId) { $this->userId = $userId; $this->mailCheckInterval = COption::getOptionString('intranet', 'mail_check_period', 10) * 60; } public function getFailedToSyncMailboxes() { $mailboxes = []; $mailboxesSyncInfo = $this->getMailboxesSyncInfo(); foreach ($mailboxesSyncInfo as $mailboxId => $lastMailCheckData) { if (!$lastMailCheckData['isSuccess']) { $mailboxes[$mailboxId] = $lastMailCheckData; } } return $mailboxes; } public function getSuccessSyncedMailboxes() { $mailboxesToSync = []; $mailboxesSyncInfo = $this->getMailboxesSyncInfo(); foreach ($mailboxesSyncInfo as $mailboxId => $lastMailCheckData) { if ($lastMailCheckData['isSuccess']) { $mailboxesToSync[$mailboxId] = $lastMailCheckData; } } return $mailboxesToSync; } public function getNeedToBeSyncedMailboxes() { $mailboxesSyncData = $this->getSuccessSyncedMailboxes(); $mailboxesToSync = []; foreach ($mailboxesSyncData as $mailboxId => $lastMailCheckData) { if ($lastMailCheckData['timeStarted'] >= 0 && (time() - intval($lastMailCheckData['timeStarted']) >= $this->mailCheckInterval)) { $mailboxesToSync[$mailboxId] = $lastMailCheckData; } } return $mailboxesToSync; } public function getMailCheckInterval() { return $this->mailCheckInterval; } public function deleteSyncData($mailboxId) { $mailboxesOptions = $this->getMailboxesSyncInfo(); if (empty($mailboxesOptions)) { return; } unset($mailboxesOptions[$mailboxId]); if (empty($mailboxesOptions)) { CUserOptions::deleteOption($this->syncOptionCategory, $this->syncOptionName, false, $this->userId); } else { $this->setOption($mailboxesOptions); } } public function setDefaultSyncData($mailboxId) { $mailboxesOptions = $this->getMailboxesSyncInfo(); $mailboxesOptions[$mailboxId] = ['isSuccess' => true, 'timeStarted' => 0]; $this->setOption($mailboxesOptions); } public function setSyncStartedData($mailboxId, $time = null) { $mailboxesOptions = $this->getMailboxesSyncInfo(); $mailboxesOptions[$mailboxId] = ['isSuccess' => true, 'timeStarted' => $time !== null && (int)$time >= 0 ? (int)$time : time()]; $this->setOption($mailboxesOptions); } public function setSyncStatus($mailboxId, $isSuccess, $time = null) { $mailboxesOptions = $this->getMailboxesSyncInfo(); $mailboxesOptions[$mailboxId] = ['isSuccess' => $isSuccess, 'timeStarted' => $time !== null && (int)$time >= 0 ? (int)$time : time()]; $this->setOption($mailboxesOptions); } private function setOption($mailboxesSyncInfo) { CUserOptions::setOption($this->syncOptionCategory, $this->syncOptionName, $mailboxesSyncInfo, false, $this->userId); } /** * @return mixed */ private function getMailboxesSyncInfo() { return CUserOptions::getOption($this->syncOptionCategory, $this->syncOptionName, [], $this->userId); } public function getNextTimeToSync($lastMailCheckData) { return intval($lastMailCheckData['timeStarted']) + $this->mailCheckInterval - time(); } /** * @return null|int */ public function getFirstFailedToSyncMailboxId() { $mailboxesIdsFailedToSync = array_keys($this->getFailedToSyncMailboxes()); return !empty($mailboxesIdsFailedToSync) && count($mailboxesIdsFailedToSync) > 0 ? (int)$mailboxesIdsFailedToSync[0] : null; } public function getLastMailboxSyncIsSuccessStatus($mailboxId) { $mailboxesOptions = $this->getMailboxesSyncInfo(); if (!(isset($mailboxesOptions[$mailboxId]) && array_key_exists('isSuccess', $mailboxesOptions[$mailboxId]))) { return null; } return $mailboxesOptions[$mailboxId]['isSuccess']; } public function getLastMailboxSyncTime($mailboxId) { $mailboxesOptions = $this->getMailboxesSyncInfo(); if (!(isset($mailboxesOptions[$mailboxId]) && array_key_exists('timeStarted', $mailboxesOptions[$mailboxId]))) { return null; } return $mailboxesOptions[$mailboxId]['timeStarted']; } } mailbox/sharedmailboxesmanager.php 0000664 00000003572 14774470223 0013440 0 ustar 00 <?php namespace Bitrix\Mail\Helper\Mailbox; use Bitrix\Mail\Internals\MailboxAccessTable; use Bitrix\Mail\MailboxTable; use Bitrix\Main\Entity\Query\Filter\Expression\Column; use Bitrix\Main\Entity\ReferenceField; use Bitrix\Main\ORM\Fields\ExpressionField; use Bitrix\Main\ORM\Query\Query; class SharedMailboxesManager { public static function getSharedMailboxesCount() { $count = static::getBaseQueryForSharedMailboxes() ->addSelect(Query::expr()->countDistinct('MAILBOX_ID'), 'CNT') ->exec() ->fetch(); return !empty($count['CNT']) ? $count['CNT'] : 0; } public static function getSharedMailboxesIds() { $mailboxesIds = static::getBaseQueryForSharedMailboxes() ->addSelect('MAILBOX_ID') ->addGroup('MAILBOX_ID') ->exec() ->fetchAll(); return array_map('intval', array_column($mailboxesIds, 'MAILBOX_ID')); } public static function getUserIdsWithAccessToMailbox($mailboxId) { $userCodes = MailboxAccessTable::query() ->addSelect('ACCESS_CODE') ->where('MAILBOX_ID', $mailboxId) ->whereLike('ACCESS_CODE', 'U%') ->exec() ->fetchAll(); $results = []; foreach ($userCodes as $userAccessCode) { // @TODO: departments if (preg_match('#U[0-9]+#', $userAccessCode['ACCESS_CODE']) === 1) { $results[] = substr($userAccessCode['ACCESS_CODE'], 1); } } return $results; } /** * @return \Bitrix\Mail\Internals\EO_MailboxAccess_Query|Query * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\SystemException */ private static function getBaseQueryForSharedMailboxes() { return MailboxAccessTable::query() ->registerRuntimeField('', new ReferenceField('ref', MailboxTable::class, ['=this.MAILBOX_ID' => 'ref.ID'], ['join_type' => 'INNER'])) ->where(new ExpressionField('ac', 'CONCAT("U", %s)', 'ref.USER_ID'), '!=', new Column('ACCESS_CODE')) ->where('ref.ACTIVE', 'Y') ->where('ref.LID', SITE_ID); } } mailbox/responseparser.php 0000644 00000002033 14774470223 0011773 0 ustar 00 <?php namespace Bitrix\Mail\Helper\Mailbox; class ResponseParser { public function getOldToNewUidsMap($copyUid) { $uIds = []; $dirUidValidity = ''; if ($responseLine = stristr($copyUid, 'COPYUID')) { $data = explode(' ', stristr($copyUid, 'COPYUID')); if (isset($data[1]) && isset($data[2]) && isset($data[3])) { $dirUidValidity = $data[1]; $idsFrom = $this->getIdsSet($data[2]); $idsTo = $this->getIdsSet(str_replace(']', '', $data[3])); $uIds = array_combine($idsFrom, $idsTo); } } return [ 'uids' => $uIds, 'dirUid' => $dirUidValidity, ]; } private function getIdsSet($line) { $idsFrom = []; $idsFromParsed = explode(',', $line); foreach ($idsFromParsed as $_index => $_idFrom) { $sequence = explode(':', $_idFrom); if (count($sequence) == 2) { $idsFrom = array_merge($idsFrom, range(min($sequence[0], $sequence[1]), max($sequence[0], $sequence[1]), 1)); } elseif (count($sequence) == 1) { $idsFrom[] = intval($sequence[0]); } } return $idsFrom; } } mailbox/imap.php 0000664 00000111274 14774470223 0007660 0 ustar 00 <?php namespace Bitrix\Mail\Helper\Mailbox; use Bitrix\Mail\Helper\MessageFolder; use Bitrix\Main; use Bitrix\Mail; class Imap extends Mail\Helper\Mailbox { const MESSAGE_PARTS_TEXT = 1; const MESSAGE_PARTS_ATTACHMENT = 2; const MESSAGE_PARTS_ALL = -1; protected $client; protected function __construct($mailbox) { parent::__construct($mailbox); $mailbox = &$this->mailbox; $this->client = new Mail\Imap( $mailbox['SERVER'], $mailbox['PORT'], $mailbox['USE_TLS'] == 'Y' || $mailbox['USE_TLS'] == 'S', $mailbox['USE_TLS'] == 'Y', $mailbox['LOGIN'], $mailbox['PASSWORD'] ); } protected function normalizeMailboxOptions() { $options = &$this->mailbox['OPTIONS']; if (empty($options['imap']) || !is_array($options['imap'])) { $options['imap'] = array(); } $imapOptions = &$options['imap']; if (empty($imapOptions[MessageFolder::INCOME]) || !is_array($imapOptions[MessageFolder::INCOME])) { $imapOptions[MessageFolder::INCOME] = array(); } if (empty($imapOptions[MessageFolder::OUTCOME]) || !is_array($imapOptions[MessageFolder::OUTCOME])) { $imapOptions[MessageFolder::OUTCOME] = array(); } } public function getSyncStatus() { $meta = Mail\MailMessageUidTable::getList(array( 'select' => array( new Main\Entity\ExpressionField('TOTAL', 'COUNT(1)'), ), 'filter' => array( '=MAILBOX_ID' => $this->mailbox['ID'], ), ))->fetch(); if ($meta['TOTAL'] > 0 && $this->mailbox['OPTIONS']['imap']['total'] > 0) { return $meta['TOTAL'] / $this->mailbox['OPTIONS']['imap']['total']; } else { return parent::getSyncStatus(); } } protected function syncInternal() { $count = $this->syncMailbox(); if (false === $count) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); } return $count; } protected function createMessage(Main\Mail\Mail $message, array $fields = array()) { $dir = reset($this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]) ?: 'INBOX'; $fields = array_merge( $fields, array( 'DIR_MD5' => md5($dir), 'DIR_UIDV' => 0, 'MSG_UID' => 0, ) ); return parent::createMessage($message, $fields); } public function syncOutgoing() { $this->cacheDirs(); parent::syncOutgoing(); } public function uploadMessage(Main\Mail\Mail $message, array &$excerpt = null) { $dir = reset($this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]) ?: 'INBOX'; $data = $this->client->select($dir, $error); if (false === $data) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); return false; } if (!empty($excerpt['__unique_headers'])) { if ($this->client->searchByHeader(false, $dir, $excerpt['__unique_headers'], $error)) { return false; } } if (!empty($excerpt['ID'])) { class_exists('Bitrix\Mail\Helper'); Mail\DummyMail::overwriteMessageHeaders( $message, array( 'X-Bitrix-Mail-Message-UID' => $excerpt['ID'], ) ); } $result = $this->client->append( $dir, array('\Seen'), new \DateTime, sprintf( '%1$s%3$s%3$s%2$s', $message->getHeaders(), $message->getBody(), $message->getMailEol() ), $error ); if (false === $result) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); return false; } $this->syncDir($dir); return $result; } public function downloadMessage(array &$excerpt) { if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5'])) { return false; } $dir = MessageFolder::getFolderNameByHash($excerpt['DIR_MD5'], $this->mailbox['OPTIONS']); if (empty($dir)) { return false; } $body = $this->client->fetch(true, $dir, $excerpt['MSG_UID'], '(BODY.PEEK[])', $error); if (false === $body) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); return false; } return empty($body['BODY[]']) ? null : $body['BODY[]']; } public function downloadMessageParts(array &$excerpt, Mail\Imap\BodyStructure $bodystructure, $flags = Imap::MESSAGE_PARTS_ALL) { if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5'])) { return false; } $dir = MessageFolder::getFolderNameByHash($excerpt['DIR_MD5'], $this->mailbox['OPTIONS']); if (empty($dir)) { return false; } $rfc822Parts = array(); $select = array_filter( $bodystructure->traverse( function (Mail\Imap\BodyStructure $item) use ($flags, &$rfc822Parts) { if ($item->isMultipart()) { return; } $isTextItem = $item->isText() && !$item->isAttachment(); if ($flags & ($isTextItem ? Imap::MESSAGE_PARTS_TEXT : Imap::MESSAGE_PARTS_ATTACHMENT)) { // due to yandex bug if ('message' === $item->getType() && 'rfc822' === $item->getSubtype()) { $rfc822Parts[] = $item; return sprintf('BODY.PEEK[%1$s.HEADER] BODY.PEEK[%1$s.TEXT]', $item->getNumber()); } return sprintf('BODY.PEEK[%1$s.MIME] BODY.PEEK[%1$s]', $item->getNumber()); } }, true ) ); if (empty($select)) { return array(); } $parts = $this->client->fetch( true, $dir, $excerpt['MSG_UID'], sprintf('(%s)', join(' ', $select)), $error ); if (false === $parts) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); return false; } foreach ($rfc822Parts as $item) { $headerKey = sprintf('BODY[%s.HEADER]', $item->getNumber()); $bodyKey = sprintf('BODY[%s.TEXT]', $item->getNumber()); if (array_key_exists($headerKey, $parts) || array_key_exists($bodyKey, $parts)) { $partMime = 'Content-Type: message/rfc822'; if (!empty($item->getParams()['name'])) { $partMime .= sprintf('; name="%s"', $item->getParams()['name']); } if (!empty($item->getDisposition()[0])) { $partMime .= sprintf("\r\nContent-Disposition: ", $item->getDisposition()[0]); if (!empty($item->getDisposition()[1]) && is_array($item->getDisposition()[1])) { foreach ($item->getDisposition()[1] as $name => $value) { $partMime .= sprintf('; %s="%s"', $name, $value); } } } $parts[sprintf('BODY[%1$s.MIME]', $item->getNumber())] = $partMime; $parts[sprintf('BODY[%1$s]', $item->getNumber())] = sprintf( "%s\r\n\r\n%s", rtrim($parts[$headerKey], "\r\n"), ltrim($parts[$bodyKey], "\r\n") ); unset($parts[$headerKey], $parts[$bodyKey]); } } return $parts; } public function cacheDirs() { $dirs = $this->client->listMailboxes('*', $error, true); if (false === $dirs) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); return false; } $imapDirs = array(); $disabledDirs = array(); $availableDirs = array(); $outcomeDirs = array(); $draftsDirs = array(); $trashDirs = array(); $spamDirs = array(); $inboxDirs = array(); $inboxTrees = array(); foreach ($dirs as $i => $item) { if (strtoupper($item['name']) === 'INBOX') { if (!$inboxDirs) { $inboxDirs = [$item['name']]; } else { $disabledDirs[] = $item['name']; } } } foreach ($dirs as $i => $item) { $imapDirs[$item['name']] = $item['path']; if (in_array(reset($item['path']), $inboxDirs)) { $inboxTrees[$item['name']] = $item['path']; } if (preg_grep('/^ \x5c Noselect $/ix', $item['flags'])) { $disabledDirs[] = $item['name']; } if (preg_grep('/^ \x5c Sent $/ix', $item['flags'])) { $outcomeDirs[] = $item['name']; } if (preg_grep('/^ \x5c Drafts $/ix', $item['flags'])) { $draftsDirs[] = $item['name']; } if (preg_grep('/^ \x5c Trash $/ix', $item['flags'])) { $trashDirs[] = $item['name']; } if (preg_grep('/^ \x5c ( Junk | Spam ) $/ix', $item['flags'])) { $spamDirs[] = $item['name']; } } // @TODO: filter disabled from income, outcome etc. $this->reloadMailboxOptions(); $options = &$this->mailbox['OPTIONS']; $options['imap']['dirs'] = $imapDirs = $inboxTrees + $imapDirs; $options['imap']['disabled'] = $disabledDirs; $availableDirs = array_diff(array_keys($imapDirs), $disabledDirs); $options['imap'][MessageFolder::INCOME] = $inboxDirs; $options['imap'][MessageFolder::OUTCOME] = array_intersect( (array) $options['imap'][MessageFolder::OUTCOME], $availableDirs ) ?: $outcomeDirs; $options['imap'][MessageFolder::DRAFTS] = array_intersect( (array) $options['imap'][MessageFolder::DRAFTS], $availableDirs ) ?: $draftsDirs; $options['imap'][MessageFolder::DRAFTS] = $draftsDirs; // @TODO: remove if drafts dir settings implemented $options['imap'][MessageFolder::TRASH] = array_intersect( (array) $options['imap'][MessageFolder::TRASH], $availableDirs ) ?: $trashDirs; $options['imap'][MessageFolder::SPAM] = array_intersect( (array) $options['imap'][MessageFolder::SPAM], $availableDirs ) ?: $spamDirs; if (!empty($options['imap']['!sync_dirs'])) { $options['imap']['sync_dirs'] = $options['imap']['!sync_dirs']; } if (!empty($options['imap']['sync_dirs'])) { $options['imap']['ignore'] = array_values(array_diff( array_keys($imapDirs), (array) $options['imap']['sync_dirs'] )); unset($options['imap']['sync_dirs']); } if (!array_key_exists('ignore', $options['imap'])) { $options['imap']['ignore'] = array_merge( (array) $options['imap'][MessageFolder::DRAFTS], (array) $options['imap'][MessageFolder::TRASH], (array) $options['imap'][MessageFolder::SPAM] ); } $options['imap']['ignore'] = array_unique(array_merge( (array) $options['imap']['ignore'], $disabledDirs )); Mail\MailboxTable::update( $this->mailbox['ID'], array( 'OPTIONS' => $options, ) ); return $dirs; } public function cacheMeta() { $dirs = $this->cacheDirs(); if (false === $dirs) { return false; } $meta = array(); foreach ($dirs as $i => $item) { if (!in_array($item['name'], (array) $this->mailbox['OPTIONS']['imap']['ignore'])) { if (!preg_grep('/^ \x5c Noselect $/ix', $item['flags'])) { $data = $this->client->examine($item['name'], $error); if (false === $data) { $this->warnings->add($this->client->getErrors()->toArray()); } else { $item = array_merge($item, $data); } } } $meta[$item['name']] = $item; } $this->reloadMailboxOptions(); $options = &$this->mailbox['OPTIONS']; $options['imap']['total'] = array_reduce( $meta, function ($sum, $item) use (&$options) { return $sum + (in_array($item['name'], (array) $options['imap']['ignore']) ? 0 : $item['exists']); }, 0 ); Mail\MailboxTable::update( $this->mailbox['ID'], array( 'OPTIONS' => $options, ) ); return $meta; } protected function getFolderToMessagesMap($messages) { if (isset($messages['MSG_UID'])) { $messages = [$messages]; } $data = []; $result = new Main\Result(); foreach ($messages as $message) { $id = $message['MSG_UID']; $folderFrom = MessageFolder::getFolderNameByHash($message['DIR_MD5'], $this->mailbox['OPTIONS']); $data[$folderFrom][] = $id; $results[$folderFrom][] = $message; } return $result->setData($data); } public function markUnseen($messages) { $result = $this->getFolderToMessagesMap($messages); foreach ($result->getData() as $folderFrom => $ids) { $result = $this->client->unseen($ids, $folderFrom); if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty()) { break; } } return $result; } public function markSeen($messages) { $result = $this->getFolderToMessagesMap($messages); foreach ($result->getData() as $folderFrom => $ids) { $result = $this->client->seen($ids, $folderFrom); if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty()) { break; } } return $result; } public function moveMailsToFolder($messages, $folderTo) { $result = $this->getFolderToMessagesMap($messages); $moveResult = new Main\Result(); foreach ($result->getData() as $folderFrom => $ids) { $moveResult = $this->client->moveMails($ids, $folderFrom, $folderTo); if (!$moveResult->isSuccess() || !$this->client->getErrors()->isEmpty()) { break; } } return $moveResult; } public function deleteMails($messages) { $result = $this->getFolderToMessagesMap($messages); foreach ($result->getData() as $folderName => $messageId) { $result = $this->client->delete($messageId, $folderName); } return $result; } public function syncMailbox() { if (!$this->client->authenticate($error)) { return false; } $meta = $this->cacheMeta(); if (false === $meta) { return false; } $count = 0; $queue = array( 'inbox' => array(), MessageFolder::OUTCOME => array(), 'other' => array(), MessageFolder::TRASH => array(), MessageFolder::SPAM => array(), ); foreach ($meta as $dir => $item) { if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore'])) { continue; } if ('inbox' == strtolower(reset($item['path']))) { $queue['inbox'][$dir] = $item; } else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME])) { $queue[MessageFolder::OUTCOME][$dir] = $item; } else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::TRASH])) { $queue[MessageFolder::TRASH][$dir] = $item; } else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::SPAM])) { $queue[MessageFolder::SPAM][$dir] = $item; } else { $queue['other'][$dir] = $item; } } $meta = call_user_func_array('array_merge', $queue); foreach ($meta as $dir => $item) { if ($item['exists'] > 0) { $count += $this->syncDir($dir); if ($this->isTimeQuotaExceeded()) { break; } } } $this->lastSyncResult = ['newMessages' => $count, 'updatedMessages' => 0, 'deletedMessages' => 0]; if (!$this->isTimeQuotaExceeded()) { $result = $this->unregisterMessages(array( '!@DIR_MD5' => array_map( 'md5', array_diff( array_keys($meta), (array) $this->mailbox['OPTIONS']['imap']['ignore'] ) ), )); $countDeleted = $result ? $result->getCount() : 0; $this->lastSyncResult['deletedMessages'] += $countDeleted; if (!empty($this->syncParams['full'])) { foreach ($meta as $dir => $item) { $this->resyncDir($dir); if ($this->isTimeQuotaExceeded()) { break; } } } } return $count; } public function syncDir($dir) { $count = 0; if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore'])) { return $count; } while ($range = $this->getSyncRange($dir, $uidtoken)) { $reverse = $range[0] > $range[1]; sort($range); $messages = $this->client->fetch( true, $dir, join(':', $range), '(UID FLAGS INTERNALDATE RFC822.SIZE BODYSTRUCTURE BODY.PEEK[HEADER])', $error ); if (empty($messages)) { if (false === $messages) { $this->warnings->add($this->client->getErrors()->toArray()); } else { // @TODO: log } break; } $reverse ? krsort($messages) : ksort($messages); $this->parseHeaders($messages); $this->blacklistMessages($dir, $messages); $this->prepareMessages($dir, $uidtoken, $messages); $hashesMap = array(); foreach ($messages as $id => $item) { if ($this->syncMessage($dir, $uidtoken, $item, $hashesMap)) { $count++; } if ($this->isTimeQuotaExceeded()) { break 2; } } } if (false === $range) { $this->warnings->add($this->client->getErrors()->toArray()); } return $count; } public function resyncDir($dir) { if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore'])) { $result = $this->unregisterMessages(array( '=DIR_MD5' => md5($dir), )); $countDeleted = $result ? $result->getCount() : 0; $this->lastSyncResult['deletedMessages'] += $countDeleted; return; } $meta = $this->client->select($dir, $error); if (false === $meta) { $this->warnings->add($this->client->getErrors()->toArray()); return; } $uidtoken = $meta['uidvalidity']; if ($meta['exists'] > 0) { if ($uidtoken > 0) { $result = $this->unregisterMessages(array( '=DIR_MD5' => md5($dir), '<DIR_UIDV' => $uidtoken, )); $countDeleted = $result ? $result->getCount() : 0; $this->lastSyncResult['deletedMessages'] += $countDeleted; } } else { if ($this->client->ensureEmpty($dir, $error)) { $result = $this->unregisterMessages(array( '=DIR_MD5' => md5($dir), )); $countDeleted = $result ? $result->getCount() : 0; $this->lastSyncResult['deletedMessages'] += $countDeleted; } return; } $fetcher = function ($range) use ($dir) { $messages = $this->client->fetch(false, $dir, $range, '(UID FLAGS)', $error); if (empty($messages)) { if (false === $messages) { $this->warnings->add($this->client->getErrors()->toArray()); } else { // @TODO: log } return; } krsort($messages); return $messages; }; $messages = $fetcher($meta['exists'] > 10000 ? sprintf('1,%u', $meta['exists']) : '1:*'); if (empty($messages)) { return; } $range = array( reset($messages)['UID'], end($messages)['UID'], ); sort($range); $result = $this->unregisterMessages(array( '=DIR_MD5' => md5($dir), '>MSG_UID' => 0, array( 'LOGIC' => 'OR', '<MSG_UID' => $range[0], '>MSG_UID' => $range[1], ), )); $countDeleted = $result ? $result->getCount() : 0; $this->lastSyncResult['deletedMessages'] += $countDeleted; if (!($meta['exists'] > 10000)) { $this->resyncMessages($dir, $uidtoken, $messages); return; } $range1 = $meta['exists']; while ($range1 > 0) { $rangeSize = $range1 > 10000 ? 8000 : $range1; $range0 = max($range1 - $rangeSize, 1); $messages = $fetcher(sprintf('%u:%u', $range0, $range1)); if (empty($messages)) { return; } $this->resyncMessages($dir, $uidtoken, $messages); if ($this->isTimeQuotaExceeded()) { return; } $range1 -= $rangeSize; } } protected function parseHeaders(&$messages) { foreach ($messages as $id => $item) { $messages[$id]['__header'] = \CMailMessage::parseHeader($item['BODY[HEADER]'], $this->mailbox['LANG_CHARSET']); $messages[$id]['__from'] = array_unique(array_map('strtolower', array_filter(array_merge( \CMailUtil::extractAllMailAddresses($messages[$id]['__header']->getHeader('FROM')), \CMailUtil::extractAllMailAddresses($messages[$id]['__header']->getHeader('REPLY-TO')) ), 'trim'))); } } protected function blacklistMessages($dir, &$messages) { $trashDirs = (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::TRASH]; $spamDirs = (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::SPAM]; $targetDir = reset($spamDirs) ?: reset($trashDirs) ?: null; if (empty($targetDir) || in_array($dir, array_merge($trashDirs, $spamDirs))) { return; } $blacklist = array( 'email' => array(), 'domain' => array(), ); $blacklistEmails = Mail\BlacklistTable::query() ->addSelect('*') ->setFilter(array( '=SITE_ID' => $this->mailbox['LID'], array( 'LOGIC' => 'OR', '=MAILBOX_ID' => $this->mailbox['ID'], array( '=MAILBOX_ID' => 0, '@USER_ID' => array(0, $this->mailbox['USER_ID']), ), ), )) ->exec() ->fetchCollection(); foreach ($blacklistEmails as $blacklistEmail) { if ($blacklistEmail->isDomainType()) { $blacklist['domain'][] = $blacklistEmail; } else { $blacklist['email'][] = $blacklistEmail; } } if (empty($blacklist['email']) && empty($blacklist['domain'])) { return; } $targetMessages = []; $emailAddresses = array_map(function ($element) { /** @var Mail\Internals\Entity\BlacklistEmail $element */ return $element->getItemValue(); }, $blacklist['email']); $domains = array_map(function ($element) { /** @var Mail\Internals\Entity\BlacklistEmail $element */ return $element->getItemValue(); }, $blacklist['domain']); foreach ($messages as $id => $item) { if (!empty($blacklist['email'])) { if (array_intersect($messages[$id]['__from'], $emailAddresses)) { $targetMessages[$id] = $item['UID']; continue; } else { foreach ($blacklist['email'] as $blacklistMail) { /** @var Mail\Internals\Entity\BlacklistEmail $blacklistMail */ if (array_intersect($messages[$id]['__from'], [$blacklistMail->convertDomainToPunycode()])) { $targetMessages[$id] = $item['UID']; continue; } } } } if (!empty($blacklist['domain'])) { foreach ($messages[$id]['__from'] as $email) { $domain = substr($email, strrpos($email, '@')); if (in_array($domain, $domains)) { $targetMessages[$id] = $item['UID']; continue 2; } } } } if (!empty($targetMessages)) { if ($this->client->moveMails($targetMessages, $dir, $targetDir)->isSuccess()) { $messages = array_diff_key($messages, $targetMessages); } } } protected function prepareMessages($dir, $uidtoken, &$messages) { $excerpt = array(); $range = array( reset($messages)['UID'], end($messages)['UID'], ); sort($range); $result = $this->listMessages(array( 'select' => array('ID'), 'filter' => array( '=DIR_MD5' => md5($dir), '=DIR_UIDV' => $uidtoken, '>=MSG_UID' => $range[0], '<=MSG_UID' => $range[1], ), ), false); while ($item = $result->fetch()) { $excerpt[] = $item['ID']; } $uids = array(); $hashes = array(); foreach ($messages as $id => $item) { $messageUid = md5(sprintf('%s:%u:%u', $dir, $uidtoken, $item['UID'])); if (in_array($messageUid, $excerpt)) { unset($messages[$id]); continue; } $excerpt[] = $uids[$id] = $messageUid; $hashes[$id] = md5(sprintf( '%s:%s:%u', trim($item['BODY[HEADER]']), $item['INTERNALDATE'], $item['RFC822.SIZE'] )); $messages[$id]['__internaldate'] = Main\Type\DateTime::createFromPhp( \DateTime::createFromFormat( 'j-M-Y H:i:s O', ltrim(trim($item['INTERNALDATE']), '0') ) ?: new \DateTime ); $messages[$id]['__fields'] = array( 'ID' => $messageUid, 'DIR_MD5' => md5($dir), 'DIR_UIDV' => $uidtoken, 'MSG_UID' => $item['UID'], 'INTERNALDATE' => $messages[$id]['__internaldate'], 'IS_SEEN' => preg_grep('/^ \x5c Seen $/ix', $item['FLAGS']) ? 'Y' : 'N', 'HEADER_MD5' => $hashes[$id], 'MESSAGE_ID' => 0, ); if (preg_match('/X-Bitrix-Mail-Message-UID:\s*([a-f0-9]+)/i', $item['BODY[HEADER]'], $matches)) { $messages[$id]['__replaces'] = $matches[1]; } } $hashesMap = array(); foreach ($hashes as $id => $hash) { if (!array_key_exists($hash, $hashesMap)) { $hashesMap[$hash] = array(); } $hashesMap[$hash][] = $id; } $result = $this->listMessages(array( 'select' => array('HEADER_MD5', 'MESSAGE_ID', 'DATE_INSERT'), 'filter' => array( '@HEADER_MD5' => array_keys($hashesMap), ), ), false); while ($item = $result->fetch()) { foreach ((array) $hashesMap[$item['HEADER_MD5']] as $id) { $messages[$id]['__created'] = $item['DATE_INSERT']; $messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID']; } } $result = $this->listMessages(array( 'select' => array('ID', 'MESSAGE_ID', 'DATE_INSERT'), 'filter' => array( '@ID' => array_values($uids), // DIR_MD5 can be empty in DB ), ), false); while ($item = $result->fetch()) { $id = array_search($item['ID'], $uids); $messages[$id]['__created'] = $item['DATE_INSERT']; $messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID']; $messages[$id]['__replaces'] = $item['ID']; } } protected function resyncMessages($dir, $uidtoken, &$messages) { $excerpt = array(); $range = array( reset($messages)['UID'], end($messages)['UID'], ); sort($range); $result = $this->listMessages(array( 'select' => array('ID', 'MESSAGE_ID', 'IS_SEEN'), 'filter' => array( '=DIR_MD5' => md5($dir), '=DIR_UIDV' => $uidtoken, '>=MSG_UID' => $range[0], '<=MSG_UID' => $range[1], ), ), false); while ($item = $result->fetch()) { $item['MAILBOX_USER_ID'] = $this->mailbox['USER_ID']; $excerpt[$item['ID']] = $item; } $update = array( 'Y' => array(), 'N' => array(), 'S' => array(), 'U' => array(), ); foreach ($messages as $id => $item) { $messageUid = md5(sprintf('%s:%u:%u', $dir, $uidtoken, $item['UID'])); if (array_key_exists($messageUid, $excerpt)) { $excerptSeen = $excerpt[$messageUid]['IS_SEEN']; $excerptSeenYN = in_array($excerptSeen, array('Y', 'S')) ? 'Y' : 'N'; $messageSeen = preg_grep('/^ \x5c Seen $/ix', $item['FLAGS']) ? 'Y' : 'N'; if ($messageSeen != $excerptSeen) { if (in_array($excerptSeen, array('S', 'U'))) { $excerpt[$messageUid]['IS_SEEN'] = $excerptSeenYN; $update[$excerptSeenYN][$messageUid] = $excerpt[$messageUid]; if ($messageSeen != $excerptSeenYN) { $update[$excerptSeen][] = $item['UID']; } } else { $excerpt[$messageUid]['IS_SEEN'] = $messageSeen; $update[$messageSeen][$messageUid] = $excerpt[$messageUid]; } } unset($excerpt[$messageUid]); } else { /* addMessage2Log( sprintf( 'IMAP: message lost (%u:%s:%u:%s)', $this->mailbox['ID'], $dir, $uidtoken, $item['UID'] ), 'mail', 0, false ); */ } } $countUpdated = 0; $countDeleted = count($excerpt); foreach ($update as $seen => $items) { if (!empty($items)) { if (in_array($seen, array('S', 'U'))) { $method = 'S' == $seen ? 'seen' : 'unseen'; $this->client->$method($items, $dir); } else { $countUpdated += count($items); $this->updateMessagesRegistry( array( '@ID' => array_keys($items), ), array( 'IS_SEEN' => $seen, ), $items = array() // @TODO: fix lazyload in MessageEventManager::processOnMailMessageModified() ); } } } if (!empty($excerpt)) { $result = $this->unregisterMessages( array( '@ID' => array_keys($excerpt), ), $excerpt ); $countDeleted += $result ? $result->getCount() : 0; } $this->lastSyncResult['updatedMessages'] += $countUpdated; $this->lastSyncResult['deletedMessages'] += $countDeleted; } protected function syncMessage($dir, $uidtoken, $message, &$hashesMap = array()) { $fields = $message['__fields']; if ($fields['MESSAGE_ID'] > 0) { $hashesMap[$fields['HEADER_MD5']] = $fields['MESSAGE_ID']; } else { if (array_key_exists($fields['HEADER_MD5'], $hashesMap) && $hashesMap[$fields['HEADER_MD5']] > 0) { $fields['MESSAGE_ID'] = $hashesMap[$fields['HEADER_MD5']]; } } if (!$this->registerMessage($fields, isset($message['__replaces']) ? $message['__replaces'] : null)) { return false; } if (Mail\Helper\LicenseManager::getSyncOldLimit() > 0) { if ($message['__internaldate']->getTimestamp() < strtotime(sprintf('-%u days', Mail\Helper\LicenseManager::getSyncOldLimit()))) { return false; } } if (!empty($this->mailbox['OPTIONS']['sync_from'])) { if ($message['__internaldate']->getTimestamp() < $this->mailbox['OPTIONS']['sync_from']) { return false; } } if (!empty($message['__created']) && !empty($this->mailbox['OPTIONS']['resync_from'])) { if ($message['__created']->getTimestamp() < $this->mailbox['OPTIONS']['resync_from']) { return false; } } if ($fields['MESSAGE_ID'] > 0) { return true; } $messageId = 0; if (!empty($message['BODYSTRUCTURE']) && !empty($message['BODY[HEADER]'])) { $message['__bodystructure'] = new Mail\Imap\BodyStructure($message['BODYSTRUCTURE']); $message['__parts'] = $this->downloadMessageParts( $message['__fields'], $message['__bodystructure'], $this->isSupportLazyAttachments() ? self::MESSAGE_PARTS_TEXT : self::MESSAGE_PARTS_ALL ); // #119474 if (!$message['__bodystructure']->isMultipart()) { if (is_array($message['__parts']) && !empty($message['__parts']['BODY[1]'])) { $message['__parts']['BODY[1.MIME]'] = $message['BODY[HEADER]']; } } } else { // fallback $message['__parts'] = $this->downloadMessage($message['__fields']) ?: false; } if (false !== $message['__parts']) { $messageId = $this->cacheMessage( $message, array( 'timestamp' => $message['__internaldate']->getTimestamp(), 'size' => $message['RFC822.SIZE'], 'outcome' => in_array($this->mailbox['EMAIL'], $message['__from']), 'draft' => in_array($dir, $this->mailbox['OPTIONS']['imap'][MessageFolder::DRAFTS]) || preg_grep('/^ \x5c Draft $/ix', $message['FLAGS']), 'trash' => in_array($dir, $this->mailbox['OPTIONS']['imap'][MessageFolder::TRASH]), 'spam' => in_array($dir, $this->mailbox['OPTIONS']['imap'][MessageFolder::SPAM]), 'seen' => $fields['IS_SEEN'] == 'Y', 'hash' => $fields['HEADER_MD5'], 'lazy_attachments' => $this->isSupportLazyAttachments(), 'excerpt' => $fields, ) ); } if ($messageId > 0) { $hashesMap[$fields['HEADER_MD5']] = $messageId; $this->linkMessage($fields['ID'], $messageId); } return $messageId > 0; } public function downloadAttachments(array &$excerpt) { if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5'])) { return false; } $dir = MessageFolder::getFolderNameByHash($excerpt['DIR_MD5'], $this->mailbox['OPTIONS']); if (empty($dir)) { return false; } $message = $this->client->fetch(true, $dir, $excerpt['MSG_UID'], '(BODYSTRUCTURE)', $error); if (empty($message['BODYSTRUCTURE'])) { // @TODO: fallback if (false === $message) { $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray()); } return false; } if (!is_array($message['BODYSTRUCTURE'])) { $this->errors = new Main\ErrorCollection(array( new Main\Error('Helper\Mailbox\Imap: Invalid BODYSTRUCTURE', 0), new Main\Error((string) $message['BODYSTRUCTURE'], -1), )); return false; } $message['__bodystructure'] = new Mail\Imap\BodyStructure($message['BODYSTRUCTURE']); $parts = $this->downloadMessageParts( $excerpt, $message['__bodystructure'], self::MESSAGE_PARTS_ATTACHMENT ); $attachments = array(); $message['__bodystructure']->traverse( function (Mail\Imap\BodyStructure $item) use (&$parts, &$attachments) { if ($item->isMultipart() || $item->isText() && !$item->isAttachment()) { return; } $attachments[] = \CMailMessage::decodeMessageBody( \CMailMessage::parseHeader( $parts[sprintf('BODY[%s.MIME]', $item->getNumber())], $this->mailbox['LANG_CHARSET'] ), $parts[sprintf('BODY[%s]', $item->getNumber())], $this->mailbox['LANG_CHARSET'] ); } ); return $attachments; } protected function cacheMessage(&$message, $params = array()) { if (!is_array($message)) { return parent::cacheMessage($message, $params); } if (!is_array($message['__parts'])) { return parent::cacheMessage($message['__parts'], $params); } if (empty($message['__header'])) { return false; } if (empty($message['__bodystructure']) || !($message['__bodystructure'] instanceof Mail\Imap\BodyStructure)) { return false; } $complete = function (&$html, &$text) { if ('' !== $html && '' === $text) { $text = html_entity_decode( htmlToTxt($html), ENT_QUOTES | ENT_HTML401, $this->mailbox['LANG_CHARSET'] ); } else if ('' === $html && '' !== $text) { $html = txtToHtml($text, false, 120); } }; list($bodyHtml, $bodyText, $attachments) = $message['__bodystructure']->traverse( function (Mail\Imap\BodyStructure $item, &$subparts) use (&$message, &$complete) { $parts = &$message['__parts']; $html = ''; $text = ''; $attachments = array(); if ($item->isMultipart()) { if ('alternative' === $item->getSubtype()) { foreach ($subparts as $part) { $part = $part[0]; if ('' !== $part[0]) { $html = $part[0]; } if ('' !== $part[1]) { $text = $part[1]; } if (!empty($part[2])) { $attachments = array_merge($attachments, $part[2]); } } $complete($html, $text); } else { foreach ($subparts as $part) { $part = $part[0]; $complete($part[0], $part[1]); if ('' !== $part[0] || '' !== $part[1]) { $html .= $part[0] . "\r\n\r\n"; $text .= $part[1] . "\r\n\r\n"; } $attachments = array_merge($attachments, $part[2]); } } $html = trim($html); $text = trim($text); } else { if (array_key_exists(sprintf('BODY[%s]', $item->getNumber()), $parts)) { $part = \CMailMessage::decodeMessageBody( \CMailMessage::parseHeader( $parts[sprintf('BODY[%s.MIME]', $item->getNumber())], $this->mailbox['LANG_CHARSET'] ), $parts[sprintf('BODY[%s]', $item->getNumber())], $this->mailbox['LANG_CHARSET'] ); } if (!$item->isText() || $item->isAttachment()) { $attachments[] = empty($part) ? $item->getNumber() : $part; } else if (!empty($part)) { if ('html' === $item->getSubtype()) { $html = $part['BODY']; } else { $text = $part['BODY']; } } } return array($html, $text, $attachments); } )[0]; $complete($bodyHtml, $bodyText); $dummyBody; return \CMailMessage::saveMessage( $this->mailbox['ID'], $dummyBody, $message['__header'], $bodyHtml, $bodyText, $attachments, $params ); } protected function getSyncRange($dir, &$uidtoken) { $meta = $this->client->select($dir, $error); if (false === $meta) { $this->warnings->add($this->client->getErrors()->toArray()); return null; } if (!($meta['exists'] > 0)) { return null; } $uidtoken = $meta['uidvalidity']; $rangeGetter = function ($min, $max) use ($dir, $uidtoken, &$rangeGetter) { $size = $max - $min + 1; $set = array(); $d = $size < 1000 ? 100 : pow(10, round(ceil(log10($size) - 0.7) / 2) * 2 - 2); for ($i = $min; $i <= $max; $i = $i + $d) { $set[] = $i; } if (count($set) > 1 && end($set) + 100 >= $max) { array_pop($set); } $set[] = $max; $set = $this->client->fetch(false, $dir, join(',', $set), '(UID)', $error); if (empty($set)) { return false; } ksort($set); static $uidMin, $uidMax; if (!isset($uidMin, $uidMax)) { $minmax = $this->getUidRange($dir, $uidtoken); if ($minmax) { $uidMin = $minmax['MIN']; $uidMax = $minmax['MAX']; } else { $uidMin = $uidMax = (end($set)['UID'] + 1); } } if (count($set) == 1) { $uid = reset($set)['UID']; if ($uid > $uidMax || $uid < $uidMin) { return array($uid, $uid); } } else if (end($set)['UID'] > $uidMax) { $max = current($set)['id']; do { $exmax = $max; $max = current($set)['id']; $min = prev($set)['id']; } while (current($set)['UID'] > $uidMax && prev($set) && next($set)); if ($max - $min > 200) { return $rangeGetter($min, $max); } else { if ($set[$max]['UID'] - $uidMax < 100) { $max = $exmax; } return array( max($set[$min]['UID'], $uidMax + 1), $set[$max]['UID'], ); } } else if (reset($set)['UID'] < $uidMin) { $min = current($set)['id']; do { $exmin = $min; $min = current($set)['id']; $max = next($set)['id']; } while (current($set)['UID'] < $uidMin && next($set) && prev($set)); if ($max - $min > 200) { return $rangeGetter($min, $max); } else { if ($uidMin - $set[$min]['UID'] < 100) { $min = $exmin; } return array( min($set[$max]['UID'], $uidMin - 1), $set[$min]['UID'], ); } } return null; }; return $rangeGetter(1, $meta['exists']); } protected function getUidRange($dir, $uidtoken) { $filter = array( '=DIR_MD5' => md5($dir), '=DIR_UIDV' => $uidtoken, '>MSG_UID' => 0, ); $min = $this->listMessages( array( 'select' => array( 'MIN' => 'MSG_UID', ), 'filter' => $filter, 'order' => array( 'MSG_UID' => 'ASC', ), 'limit' => 1, ), false )->fetch(); $max = $this->listMessages( array( 'select' => array( 'MAX' => 'MSG_UID', ), 'filter' => $filter, 'order' => array( 'MSG_UID' => 'DESC', ), 'limit' => 1, ), false )->fetch(); if ($min && $max) { return array( 'MIN' => $min['MIN'], 'MAX' => $max['MAX'], ); } return null; } } messageeventmanager.php 0000664 00000014102 14774470223 0011310 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Mail\MailMessageTable; use Bitrix\Mail\MailMessageUidTable; use Bitrix\Main\Event; use Bitrix\Main\Loader; use Bitrix\Main\Type\DateTime; class MessageEventManager { const EVENT_DELETE_MESSAGES = 'onMailMessageDeleted'; /** Dispatches OnMessageModified event for compatibility * If event parameters do not have ['HEADER_MD5', 'MAILBOX_USER_ID'] * data will be obtained from database * @param Event $event * @return MessageEventManager */ public static function onMailMessageDeleted(Event $event) { $manager = new static(); $manager->processOnMailMessageDeletedEvent($event); return $manager; } private function processOnMailMessageDeletedEvent(Event $event) { $params = $event->getParameters(); $filter = empty($params['DELETED_BY_FILTER']) ? [] : $params['DELETED_BY_FILTER']; $fieldsData = empty($params['MAIL_FIELDS_DATA']) ? [] : $params['MAIL_FIELDS_DATA']; $this->handleRemovedEvent($fieldsData, $filter); } /** Dispatches OnMessageObsolete event for compatibility * If messages data from event parameters do not have ['HEADER_MD5', 'MAILBOX_USER_ID', 'IS_SEEN'] * data will be obtained from database * @param Event $event * @return MessageEventManager */ public static function onMailMessageModified(Event $event) { $manager = new static(); $manager->processOnMailMessageModified($event); return $manager; } private function processOnMailMessageModified(Event $event) { $params = $event->getParameters(); $updatedFieldValues = empty($params['UPDATED_FIELDS_VALUES']) ? [] : $params['UPDATED_FIELDS_VALUES']; $fieldsData = empty($params['MAIL_FIELDS_DATA']) ? [] : $params['MAIL_FIELDS_DATA']; $filter = empty($params['UPDATED_BY_FILTER']) ? [] : $params['UPDATED_BY_FILTER']; if (!empty($updatedFieldValues) && isset($updatedFieldValues['IS_SEEN'])) { $fieldsData = $this->getMailsFieldsData($fieldsData, ['HEADER_MD5', 'MAILBOX_USER_ID', 'IS_SEEN'], $filter); $this->sendMessageModifiedEvent($fieldsData); } if (!empty($updatedFieldValues) && isset($updatedFieldValues['DIR_MD5'])) { $folderHash = empty($updatedFieldValues['DIR_MD5']) ? null : $updatedFieldValues['DIR_MD5']; $mailboxOptions = !empty($fieldsData[0]) && !empty($fieldsData[0]['MAILBOX_OPTIONS']) ? $fieldsData[0]['MAILBOX_OPTIONS'] : []; if (!empty($folderHash) && !empty($mailboxOptions)) { $isTrashFolder = $folderHash === MessageFolder::getFolderHashByType(MessageFolder::TRASH, $mailboxOptions); $isSpamFolder = $folderHash === MessageFolder::getFolderHashByType(MessageFolder::SPAM, $mailboxOptions); $folderName = MessageFolder::getFolderNameByHash($folderHash, $mailboxOptions); $isDisabledFolder = MessageFolder::isDisabledFolder($folderName, $mailboxOptions); if ($isTrashFolder || $isSpamFolder || $isDisabledFolder) { $this->handleRemovedEvent($fieldsData, $filter); } } } } protected function sendMessageModifiedEvent($fieldsData) { foreach ($fieldsData as $fields) { $event = new Event( 'mail', 'OnMessageModified', [ 'user' => $fields['MAILBOX_USER_ID'], 'hash' => $fields['HEADER_MD5'], 'seen' => $fields['IS_SEEN'] === 'Y', ] ); $event->send(); } } private function handleRemovedEvent($fieldsData, $filter) { $this->sendMessageDeletedEvent($fieldsData); } protected function sendMessageDeletedEvent($fieldsData) { foreach ($fieldsData as $fields) { $event = new Event( 'mail', 'OnMessageObsolete', [ 'user' => $fields['MAILBOX_USER_ID'], 'hash' => $fields['HEADER_MD5'], ] ); $event->send(); } } private function getMailsFieldsData($eventData, $requiredKeys, $filter) { $fieldsData = $eventData; $missingKeys = $requiredKeys; $messagesCount = count($eventData); if ($messagesCount) { foreach ($requiredKeys as $requiredKey) { if (count(array_column($eventData, $requiredKey)) === $messagesCount) { $missingKeys = array_diff($missingKeys, [$requiredKey]); } } } if (!empty($missingKeys) && !empty($filter)) { $fieldsData = $this->getMailMessagesList($filter, $missingKeys); } $results = []; foreach ($fieldsData as $index => $mailFieldsData) { $results[$mailFieldsData['HEADER_MD5']] = $mailFieldsData; } return $results; } protected function getMailMessagesList($filter, $selectingFields) { $dateLastMonth = new DateTime(); $dateLastMonth->add('-1 MONTH'); foreach ($selectingFields as $index => $selectingField) { if (strncmp('MAILBOX_', $selectingField, 8) === 0) { $selectingFields[$selectingField] = 'MAILBOX.' . substr($selectingField, 8); unset($selectingFields[$index]); } } return MailMessageUidTable::getList([ 'select' => $selectingFields, 'filter' => array_merge($filter, [ '>=INTERNALDATE' => $dateLastMonth, ]), ] )->fetchAll(); } /** * @param array $data * @return array * @throws \Bitrix\Main\ObjectException * @throws \Exception */ public static function onMailEventMailRead(array $data) { $messageId = $data['msgid']; if($messageId) { $message = MailMessageTable::getList([ 'select' => [ 'OPTIONS', 'ID', 'READ_CONFIRMED', ], 'filter' => [ '=MSG_ID' => $messageId, 'READ_CONFIRMED' => null, ] ])->fetch(); if($message) { $readTime = new DateTime(); $result = MailMessageTable::update($message['ID'], [ 'READ_CONFIRMED' => $readTime, ]); if($result->isSuccess()) { if(Loader::includeModule("pull")) { \CPullWatch::addToStack(static::getPullTagName($message['ID']), [ 'module_id' => 'mail', 'command' => 'onMessageRead', 'params' => [ 'messageId' => $message['ID'], 'readTime' => $readTime->getTimestamp(), ], ]); } } } } return $data; } public static function getPullTagName($messageId) { return 'MAILMESSAGEREADED'.$messageId; } public static function getRequiredFieldNamesForEvent($eventName) { if ($eventName === static::EVENT_DELETE_MESSAGES) { return array('HEADER_MD5', 'MESSAGE_ID', 'MAILBOX_USER_ID'); } } } messagefolder.php 0000664 00000005066 14774470223 0010120 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main\Localization\Loc; /** * Class MessageFolder */ class MessageFolder { const TRASH = 'trash'; const SPAM = 'spam'; const INCOME = 'income'; const OUTCOME = 'outcome'; const DRAFTS = 'drafts'; /** * @param array $message * @param array $mailboxOptions * @return string */ public static function getFolderNameByHash($messageFolderHash, $mailboxOptions) { $folderName = ''; if (!empty($mailboxOptions['imap']['dirsMd5'])) { $names = array_filter( $mailboxOptions['imap']['dirsMd5'], function ($hash) use ($messageFolderHash) { return $hash == $messageFolderHash; } ); if (count($names) == 1) { $folderName = array_keys($names)[0]; } } return $folderName; } public static function getFolderHashByType($folderType, $mailboxOptions) { $folderHash = ''; if (!empty($mailboxOptions['imap']['dirsMd5'])) { $name = static::getFolderNameByType($folderType, $mailboxOptions); $hashes = array_filter( $mailboxOptions['imap']['dirsMd5'], function ($_name) use ($name) { return $_name == $name; }, ARRAY_FILTER_USE_KEY ); if (count($hashes) == 1) { $folderHash = array_values($hashes)[0]; } } return $folderHash; } public static function getFolderNameByType($folderType, $mailboxOptions) { if (!empty($mailboxOptions['imap']) && is_array($mailboxOptions['imap'])) { $imapOptions = $mailboxOptions['imap']; if (!empty($imapOptions[$folderType]) && isset($imapOptions[$folderType][0])) { return $imapOptions[$folderType][0]; } } return null; } public static function getDisabledFolders($mailboxOptions) { $disabled = empty($mailboxOptions['imap']['disabled']) ? [] : $mailboxOptions['imap']['disabled']; $ignore = empty($mailboxOptions['imap']['ignore']) ? [] : $mailboxOptions['imap']['ignore']; return array_merge($disabled, $ignore); } public static function isDisabledFolder($folder, $mailboxOptions) { return in_array($folder, static::getDisabledFolders($mailboxOptions), true); } public static function getFormattedPath(array $path, $mailboxOptions) { $root = array_shift($path); if (strtolower($root) == 'inbox' && !static::isDisabledFolder($root, $mailboxOptions)) { $root = Loc::getMessage('MAIL_CLIENT_INBOX_ALIAS'); } array_unshift($path, $root); return $path; } public static function getFormattedName(array $path, $mailboxOptions, $full = true) { $path = static::getFormattedPath($path, $mailboxOptions); return $full ? join(' / ', $path) : end($path); } } recipient.php 0000664 00000014065 14774470223 0007261 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Mail; /** * Class Recipient * @package Bitrix\Mail\Helper */ class Recipient { /** * Builds unique code for BX.UI.Selector selector. * * @param string $email Email. * * @return string */ public static function buildUniqueEmailCode($email) { // return 'U' . md5($email); return 'MC' . $email; } /** * Load last used Rcpt. * * @param string $emailTo Add this email to the result list. * @param integer $limit Limit list length. * * @return array Data preformed for BX.UI.Selector selector with email only mode enabled. * * @throws Main\SystemException */ public static function loadLastRcpt($emailTo = null, $limit = 10) { global $APPLICATION; $result = array(); $currentUser = \Bitrix\Main\Engine\CurrentUser::get(); $lastRcptResult = \Bitrix\Main\FinderDestTable::getList(array( 'filter' => array( '=USER_ID' => $currentUser->getId(), '=CONTEXT' => 'MAIL_LAST_RCPT', ), 'select' => array('CODE'), 'order' => array('LAST_USE_DATE' => 'DESC'), 'limit' => $limit, )); $emailUsersIds = array(); while ($item = $lastRcptResult->fetch()) { $emailUsersIds[] = (int) str_replace('MC', '', $item['CODE']); } if (count($emailUsersIds) > 0) { $mailContacts = Mail\Internals\MailContactTable::getList([ 'filter' => array( '@ID' => $emailUsersIds, '=USER_ID' => $currentUser->getId(), ), 'select' => ['ID', 'NAME', 'EMAIL', 'ICON'], 'limit' => $limit, ])->fetchAll(); $contactAvatars = $resultsMailContacts = []; foreach ($mailContacts as $mailContact) { $resultsMailContacts[$mailContact['EMAIL']] = $mailContact; } foreach ($resultsMailContacts as $mailContact) { $email = $mailContact['EMAIL']; if ($contactAvatars[$email] === null) { ob_start(); $APPLICATION->includeComponent('bitrix:mail.contact.avatar', '', array( 'mailContact' => $mailContact, )); $contactAvatars[$email] = ob_get_clean(); } $id = static::buildUniqueEmailCode($email); $result[$id] = [ 'id' => $id, 'entityType' => 'email', 'entityId' => $mailContact['ID'], 'name' => htmlspecialcharsbx($mailContact['NAME']), 'iconCustom' => $contactAvatars[$email], 'email' => htmlspecialcharsbx($mailContact['EMAIL']), 'desc' => htmlspecialcharsbx($mailContact['EMAIL']), 'isEmail' => 'Y', ]; } } return $result; } /** * Load mail contacts - users with EXTERNAL_AUTH_ID = email. * * @param array $filter Filter. * @param integer $limit Limit list length. * * @return array Data preformed for BX.UI.Selector selector with email only mode enabled. * * @throws Main\SystemException */ public static function loadMailContacts($filter = [], $limit = 20) { global $APPLICATION; $result = array(); $mailContacts = \Bitrix\Main\UserTable::getList(array( 'select' => array( 'ID', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'EMAIL', 'PERSONAL_PHOTO', ), 'filter' => array_merge( array( '=ACTIVE' => 'Y', '=EXTERNAL_AUTH_ID' => 'email', ), $filter ), 'order' => array( 'LAST_NAME' => 'ASC', ), 'limit' => $limit, )); $contactAvatars = array(); while ($mailContact = $mailContacts->fetch()) { $email = $mailContact['EMAIL']; if ($contactAvatars[$email] === null) { ob_start(); $APPLICATION->includeComponent('bitrix:mail.contact.avatar', '', [ 'mailContact' => array( 'FILE_ID' => $mailContact['PERSONAL_PHOTO'], 'name' => \CUser::formatName(\CSite::getNameFormat(), $mailContact), 'email' => $mailContact['EMAIL'], ), ]); $contactAvatars[$email] = ob_get_clean(); } $id = static::buildUniqueEmailCode($email); $result[$id] = array( 'id' => $id, 'entityType' => 'mailContacts', 'entityId' => $mailContact['ID'], 'name' => \CUser::formatName(\CSite::getNameFormat(), $mailContact, true, true), 'iconCustom' => $contactAvatars[$email], 'email' => htmlspecialcharsbx($mailContact['EMAIL']), 'desc' => htmlspecialcharsbx($mailContact['EMAIL']), 'isEmail' => 'Y', ); } return $result; } /** * Load mail contacts from CRM - users with EXTERNAL_AUTH_ID = email and contact crm entity. * * @param array $filter Filter. * @param integer $limit Limit list length. * @return array Data preformed for BX.UI.Selector selector with email only mode enabled. * * @throws Main\LoaderException */ public static function loadCrmMailContacts($filter = [], $limit = 20) { global $APPLICATION; $result = array(); if (Main\Loader::includeModule('crm')) { $mailContacts = \Bitrix\Main\UserTable::getList(array( 'select' => array( 'ID', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'EMAIL', 'PERSONAL_PHOTO', ), 'filter' => array_merge( array( '=ACTIVE' => 'Y', '=EXTERNAL_AUTH_ID' => 'email', ), $filter ), 'order' => array( 'LAST_NAME' => 'ASC', ), 'limit' => $limit, )); $contactAvatars = array(); while ($mailContact = $mailContacts->fetch()) { $email = $mailContact['EMAIL']; if (empty($email)) { continue; } $crmCommunications = \CSocNetLogDestination::searchCrmEntities(array( 'SEARCH' => $email, 'ENTITIES' => array('CONTACT'), )); foreach ($crmCommunications as $communication) { $email = $communication['email']; if ($contactAvatars[$email] === null) { ob_start(); $APPLICATION->includeComponent('bitrix:mail.contact.avatar', '', [ 'mailContact' => array( 'FILE_ID' => $mailContact['PERSONAL_PHOTO'], 'name' => \CUser::formatName(\CSite::getNameFormat(), $mailContact), 'email' => $email, ), ]); $contactAvatars[$email] = ob_get_clean(); $communication['iconCustom'] = $contactAvatars[$email]; } $id = static::buildUniqueEmailCode($email); $communication['id'] = $id; $result[$id] = $communication; } } } return $result; } } mailbox.php 0000664 00000070302 14774470223 0006726 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Main\ORM; use Bitrix\Mail; abstract class Mailbox { const SYNC_TIMEOUT = 300; const SYNC_TIME_QUOTA = 280; protected $mailbox; protected $filters; protected $session; protected $startTime, $syncTimeout, $checkpoint; protected $syncParams = array(); protected $errors, $warnings; protected $lastSyncResult = array( 'newMessages' => 0, 'deletedMessages' => 0, 'updatedMessages' => 0 ); /** * Creates active mailbox helper instance by ID * * @param int $id Mailbox ID. * @param bool $throw Throw exception on error. * @return \Bitrix\Mail\Helper\Mailbox|false * @throws \Exception */ public static function createInstance($id, $throw = true) { return static::rawInstance(array('=ID' => (int) $id, '=ACTIVE' => 'Y'), $throw); } /** * Creates mailbox helper instance * * @param mixed $filter Filter. * @param bool $throw Throw exception on error. * @return \Bitrix\Mail\Helper\Mailbox|false * @throws \Exception */ public static function rawInstance($filter, $throw = true) { try { $mailbox = static::prepareMailbox($filter); return static::instance($mailbox); } catch (\Exception $e) { if ($throw) { throw $e; } else { return false; } } } protected static function instance(array $mailbox) { // @TODO: other SERVER_TYPE $types = array( 'imap' => 'Bitrix\Mail\Helper\Mailbox\Imap', 'controller' => 'Bitrix\Mail\Helper\Mailbox\Imap', 'domain' => 'Bitrix\Mail\Helper\Mailbox\Imap', 'crdomain' => 'Bitrix\Mail\Helper\Mailbox\Imap', ); if (empty($mailbox)) { throw new Main\ObjectException('no mailbox'); } if (empty($mailbox['SERVER_TYPE']) || !array_key_exists($mailbox['SERVER_TYPE'], $types)) { throw new Main\ObjectException('unsupported mailbox type'); } return new $types[$mailbox['SERVER_TYPE']]($mailbox); } protected static function prepareMailbox($filter) { if (is_scalar($filter)) { $filter = array('=ID' => (int) $filter); } $mailbox = Mail\MailboxTable::getList(array( 'filter' => $filter, 'select' => array('*', 'LANG_CHARSET' => 'SITE.CULTURE.CHARSET'), 'limit' => 1, ))->fetch() ?: array(); if (!empty($mailbox)) { if (in_array($mailbox['SERVER_TYPE'], array('controller', 'crdomain', 'domain'))) { $result = \CMailDomain2::getImapData(); // @TODO: request controller for 'controller' and 'crdomain' $mailbox['SERVER'] = $result['server']; $mailbox['PORT'] = $result['port']; $mailbox['USE_TLS'] = $result['secure']; } Mail\MailboxTable::normalizeEmail($mailbox); } return $mailbox; } public function setSyncParams(array $params = array()) { $this->syncParams = $params; } protected function __construct($mailbox) { $this->startTime = time(); if (defined('START_EXEC_PROLOG_BEFORE_1') && preg_match('/ (\d+)$/', START_EXEC_PROLOG_BEFORE_1, $matches)) { $startTime = $matches[1]; if ($startTime > 0 && $this->startTime >= $startTime) { $this->startTime = $startTime; } } $this->syncTimeout = min(max(0, ini_get('max_execution_time')) ?: static::SYNC_TIMEOUT, static::SYNC_TIMEOUT); $this->mailbox = $mailbox; $this->normalizeMailboxOptions(); $this->setCheckpoint(); $this->session = md5(uniqid('')); $this->errors = new Main\ErrorCollection(); $this->warnings = new Main\ErrorCollection(); } protected function reloadMailboxOptions() { $mailbox = static::prepareMailbox(array('=ID' => $this->mailbox['ID'])); if (!empty($mailbox['OPTIONS']) && is_array($mailbox['OPTIONS'])) { $this->mailbox['OPTIONS'] = $mailbox['OPTIONS']; } $this->normalizeMailboxOptions(); } protected function normalizeMailboxOptions() { if (empty($this->mailbox['OPTIONS']) || !is_array($this->mailbox['OPTIONS'])) { $this->mailbox['OPTIONS'] = array(); } } public function getMailbox() { return $this->mailbox; } protected function isTimeQuotaExceeded() { return time() - $this->startTime > ceil($this->syncTimeout * 0.9); } public function setCheckpoint() { $this->checkpoint = time(); } public function sync() { global $DB; if (!LicenseManager::isSyncAvailable()) { $this->mailbox['OPTIONS']['next_sync'] = time() + 3600 * 24; return 0; } if (time() - $this->mailbox['SYNC_LOCK'] < $this->syncTimeout) { return 0; } $this->mailbox['SYNC_LOCK'] = time(); if ($this->isTimeQuotaExceeded()) { return 0; } $this->syncOutgoing(); $lockSql = sprintf( 'UPDATE b_mail_mailbox SET SYNC_LOCK = %u WHERE ID = %u AND (SYNC_LOCK IS NULL OR SYNC_LOCK < %u)', $this->mailbox['SYNC_LOCK'], $this->mailbox['ID'], $this->mailbox['SYNC_LOCK'] - $this->syncTimeout ); if (!$DB->query($lockSql)->affectedRowsCount()) { return 0; } $mailboxSyncManager = new Mailbox\MailboxSyncManager($this->mailbox['USER_ID']); if ($this->mailbox['USER_ID'] > 0) { $mailboxSyncManager->setSyncStartedData($this->mailbox['ID']); } $this->session = md5(uniqid('')); $count = $this->syncInternal(); $success = $count !== false && $this->errors->isEmpty(); $syncUnlock = $this->isTimeQuotaExceeded() ? 0 : -1; $interval = max(1, (int) $this->mailbox['PERIOD_CHECK']) * 60; $syncErrors = max(0, (int) $this->mailbox['OPTIONS']['sync_errors']); if ($count === false) { $syncErrors++; $maxInterval = 3600 * 24 * 7; for ($i = 1; $i < $syncErrors && $interval < $maxInterval; $i++) { $interval = min($interval * ($i + 1), $maxInterval); } } else { $syncErrors = 0; $interval = $syncUnlock < 0 ? $interval : min($count > 0 ? 60 : 600, $interval); } $this->mailbox['OPTIONS']['sync_errors'] = $syncErrors; $this->mailbox['OPTIONS']['next_sync'] = time() + $interval; $optionsValue = $this->mailbox['OPTIONS']; unset($optionsValue['imap']['dirsMd5']); $unlockSql = sprintf( "UPDATE b_mail_mailbox SET SYNC_LOCK = %d, OPTIONS = '%s' WHERE ID = %u AND SYNC_LOCK = %u", $syncUnlock, $DB->forSql(serialize($optionsValue)), $this->mailbox['ID'], $this->mailbox['SYNC_LOCK'] ); if ($DB->query($unlockSql)->affectedRowsCount()) { $this->mailbox['SYNC_LOCK'] = $syncUnlock; } $lastSyncResult = $this->getLastSyncResult(); $this->pushSyncStatus( array( 'new' => $count, 'updated' => $lastSyncResult['updatedMessages'], 'deleted' => $lastSyncResult['deletedMessages'], 'complete' => $this->mailbox['SYNC_LOCK'] < 0, ), true ); if ($this->mailbox['USER_ID'] > 0) { $mailboxSyncManager->setSyncStatus($this->mailbox['ID'], $success, time()); } $usersWithAccessToMailbox = Mailbox\SharedMailboxesManager::getUserIdsWithAccessToMailbox($this->mailbox['ID']); foreach ($usersWithAccessToMailbox as $userId) { \CUserCounter::set( $userId, 'mail_unseen', Message::getTotalUnseenCount($userId), $this->mailbox['LID'] ); } return $count; } public function getSyncStatus() { return -1; } protected function pushSyncStatus($params, $force = false) { if (Main\Loader::includeModule('pull')) { $status = $this->getSyncStatus(); \CPullWatch::addToStack( 'mail_mailbox_' . $this->mailbox['ID'], array( 'module_id' => 'mail', 'command' => 'mailbox_sync_status', 'params' => array_merge( array( 'id' => $this->mailbox['ID'], 'status' => sprintf('%.3f', $status), ), $params ), ) ); if ($force) { \Bitrix\Pull\Event::send(); } } } public function dismissOldMessages() { global $DB; if (!Mail\Helper\LicenseManager::isCleanupOldEnabled()) { return true; } $startTime = time(); if (time() - $this->mailbox['SYNC_LOCK'] < $this->syncTimeout) { return false; } if ($this->isTimeQuotaExceeded()) { return false; } $syncUnlock = $this->mailbox['SYNC_LOCK']; $lockSql = sprintf( 'UPDATE b_mail_mailbox SET SYNC_LOCK = %u WHERE ID = %u AND (SYNC_LOCK IS NULL OR SYNC_LOCK < %u)', $startTime, $this->mailbox['ID'], $startTime - $this->syncTimeout ); if ($DB->query($lockSql)->affectedRowsCount()) { $this->mailbox['SYNC_LOCK'] = $startTime; } else { return false; } $result = true; $entity = Mail\MailMessageUidTable::getEntity(); $connection = $entity->getConnection(); $where = sprintf( ' (%s) AND NOT EXISTS (SELECT 1 FROM %s WHERE (%s) AND (%s)) ', ORM\Query\Query::buildFilterSql( $entity, array( '=MAILBOX_ID' => $this->mailbox['ID'], '>MESSAGE_ID' => 0, '<INTERNALDATE' => Main\Type\DateTime::createFromTimestamp(strtotime(sprintf('-%u days', Mail\Helper\LicenseManager::getSyncOldLimit()))), ) ), $connection->getSqlHelper()->quote(Mail\Internals\MessageAccessTable::getTableName()), ORM\Query\Query::buildFilterSql( $entity, array( '=MAILBOX_ID' => new Main\DB\SqlExpression('?#', 'MAILBOX_ID'), '=MESSAGE_ID' => new Main\DB\SqlExpression('?#', 'MESSAGE_ID'), ) ), ORM\Query\Query::buildFilterSql( Mail\Internals\MessageAccessTable::getEntity(), array( '=ENTITY_TYPE' => array( Mail\Internals\MessageAccessTable::ENTITY_TYPE_TASKS_TASK, Mail\Internals\MessageAccessTable::ENTITY_TYPE_BLOG_POST, ), ) ) ); do { $connection->query(sprintf( 'INSERT IGNORE INTO %s (ID, MAILBOX_ID, MESSAGE_ID) (SELECT ID, MAILBOX_ID, MESSAGE_ID FROM %s WHERE %s ORDER BY ID LIMIT 1000)', $connection->getSqlHelper()->quote(Mail\Internals\MessageDeleteQueueTable::getTableName()), $connection->getSqlHelper()->quote($entity->getDbTableName()), $where )); $connection->query(sprintf( 'UPDATE %s SET MESSAGE_ID = 0 WHERE %s ORDER BY ID LIMIT 1000', $connection->getSqlHelper()->quote($entity->getDbTableName()), $where )); if ($this->isTimeQuotaExceeded() || time() - $this->checkpoint > 20) { $result = false; break; } } while ($connection->getAffectedRowsCount() >= 1000); $unlockSql = sprintf( "UPDATE b_mail_mailbox SET SYNC_LOCK = %d WHERE ID = %u AND SYNC_LOCK = %u", $syncUnlock, $this->mailbox['ID'], $this->mailbox['SYNC_LOCK'] ); if ($DB->query($unlockSql)->affectedRowsCount()) { $this->mailbox['SYNC_LOCK'] = $syncUnlock; } return $result; } public function cleanup() { do { $res = Mail\Internals\MessageDeleteQueueTable::getList(array( 'runtime' => array( new ORM\Fields\Relations\Reference( 'MESSAGE_UID', 'Bitrix\Mail\MailMessageUidTable', array( '=this.MAILBOX_ID' => 'ref.MAILBOX_ID', '=this.MESSAGE_ID' => 'ref.MESSAGE_ID', ) ), ), 'select' => array('MESSAGE_ID', 'UID' => 'MESSAGE_UID.ID'), 'filter' => array( '=MAILBOX_ID' => $this->mailbox['ID'], ), 'limit' => 100, )); $count = 0; while ($item = $res->fetch()) { $count++; if (empty($item['UID'])) { \CMailMessage::delete($item['MESSAGE_ID']); } Mail\Internals\MessageDeleteQueueTable::deleteList(array( '=MAILBOX_ID' => $this->mailbox['ID'], '=MESSAGE_ID' => $item['MESSAGE_ID'], )); if ($this->isTimeQuotaExceeded() || time() - $this->checkpoint > 60) { return false; } } } while ($count > 0); return true; } protected function listMessages($params = array(), $fetch = true) { $filter = array( '=MAILBOX_ID' => $this->mailbox['ID'], ); if (!empty($params['filter'])) { $filter = array_merge((array) $params['filter'], $filter); } $params['filter'] = $filter; $result = Mail\MailMessageUidTable::getList($params); return $fetch ? $result->fetchAll() : $result; } protected function registerMessage(&$fields, $replaces = null) { $now = new Main\Type\DateTime(); if (!empty($replaces)) { if (!is_array($replaces)) { $replaces = array( '=ID' => $replaces, ); } $exists = Mail\MailMessageUidTable::getList(array( 'select' => array( 'ID', 'MESSAGE_ID', ), 'filter' => array( $replaces, '=MAILBOX_ID' => $this->mailbox['ID'], ), ))->fetch(); } if (!empty($exists)) { $fields['MESSAGE_ID'] = $exists['MESSAGE_ID']; $result = (bool) Mail\MailMessageUidTable::updateList( array( '=ID' => $exists['ID'], '=MAILBOX_ID' => $this->mailbox['ID'], ), array_merge( $fields, array( 'TIMESTAMP_X' => $now, ) ), array_merge( $exists, array( 'MAILBOX_USER_ID' => $this->mailbox['USER_ID'], ) ) ); } else { $result = Mail\MailMessageUidTable::add(array_merge( array( 'MESSAGE_ID' => 0, ), $fields, array( 'MAILBOX_ID' => $this->mailbox['ID'], 'SESSION_ID' => $this->session, 'TIMESTAMP_X' => $now, 'DATE_INSERT' => $now, ) ))->isSuccess(); } return $result; } protected function updateMessagesRegistry(array $filter, array $fields, $mailData = array()) { return Mail\MailMessageUidTable::updateList( array_merge( $filter, array( '=MAILBOX_ID' => $this->mailbox['ID'], ) ), $fields, $mailData ); } protected function unregisterMessages($filter, $eventData = []) { return Mail\MailMessageUidTable::deleteList( array_merge( $filter, array( '=MAILBOX_ID' => $this->mailbox['ID'], ) ), array_map( function ($item) { $item['MAILBOX_ID'] = $this->mailbox['ID']; return $item; }, $eventData ) ); } protected function linkMessage($uid, $id) { $result = Mail\MailMessageUidTable::update( array( 'ID' => $uid, 'MAILBOX_ID' => $this->mailbox['ID'], ), array( 'MESSAGE_ID' => $id, ) ); return $result->isSuccess(); } protected function cacheMessage(&$body, $params = array()) { if (empty($params['origin']) && empty($params['replaces'])) { $params['lazy_attachments'] = $this->isSupportLazyAttachments(); } return \CMailMessage::addMessage( $this->mailbox['ID'], $body, $this->mailbox['CHARSET'] ?: $this->mailbox['LANG_CHARSET'], $params ); } public function mail(array $params) { class_exists('Bitrix\Mail\Helper'); $message = new Mail\DummyMail($params); $messageUid = $this->createMessage($message); Mail\Internals\MessageUploadQueueTable::add(array( 'ID' => $messageUid, 'MAILBOX_ID' => $this->mailbox['ID'], )); \CAgent::addAgent( sprintf( 'Bitrix\Mail\Helper::syncOutgoingAgent(%u);', $this->mailbox['ID'] ), 'mail', 'N', 60 ); } protected function createMessage(Main\Mail\Mail $message, array $fields = array()) { $messageUid = sprintf('%x%x', time(), rand(0, 0xffffffff)); $messageId = $this->cacheMessage( sprintf( '%1$s%3$s%3$s%2$s', $message->getHeaders(), $message->getBody(), $message->getMailEol() ), array( 'outcome' => true, 'draft' => false, 'trash' => false, 'spam' => false, 'seen' => true, 'trackable' => true, 'origin' => true, ) ); $fields = array_merge( $fields, array( 'ID' => $messageUid, 'INTERNALDATE' => new Main\Type\DateTime, 'IS_SEEN' => 'Y', 'MESSAGE_ID' => $messageId, ) ); $this->registerMessage($fields); return $messageUid; } public function syncOutgoing() { $res = $this->listMessages( array( 'runtime' => array( new \Bitrix\Main\Entity\ReferenceField( 'UPLOAD_QUEUE', 'Bitrix\Mail\Internals\MessageUploadQueueTable', array( '=this.ID' => 'ref.ID', '=this.MAILBOX_ID' => 'ref.MAILBOX_ID', ), array( 'join_type' => 'INNER', ) ), ), 'select' => array( '*', '__' => 'MESSAGE.*', 'UPLOAD_LOCK' => 'UPLOAD_QUEUE.SYNC_LOCK', 'UPLOAD_STAGE' => 'UPLOAD_QUEUE.SYNC_STAGE', 'UPLOAD_ATTEMPTS' => 'UPLOAD_QUEUE.ATTEMPTS', ), 'filter' => array( '>=UPLOAD_QUEUE.SYNC_STAGE' => 0, '<UPLOAD_QUEUE.SYNC_LOCK' => time() - $this->syncTimeout, '<UPLOAD_QUEUE.ATTEMPTS' => 5, ), 'order' => array( 'UPLOAD_QUEUE.SYNC_LOCK' => 'ASC', 'UPLOAD_QUEUE.SYNC_STAGE' => 'ASC', 'UPLOAD_QUEUE.ATTEMPTS' => 'ASC', ), ), false ); while ($excerpt = $res->fetch()) { $n = $excerpt['UPLOAD_ATTEMPTS'] + 1; $interval = min($this->syncTimeout * pow($n, $n), 3600 * 24 * 7); if ($excerpt['UPLOAD_LOCK'] > time() - $interval) { continue; } $this->syncOutgoingMessage($excerpt); if ($this->isTimeQuotaExceeded()) { break; } } } protected function syncOutgoingMessage($excerpt) { global $DB; $lockSql = sprintf( "UPDATE b_mail_message_upload_queue SET SYNC_LOCK = %u, SYNC_STAGE = %u, ATTEMPTS = ATTEMPTS + 1 WHERE ID = '%s' AND MAILBOX_ID = %u AND SYNC_LOCK < %u", $syncLock = time(), max(1, $excerpt['UPLOAD_STAGE']), $DB->forSql($excerpt['ID']), $excerpt['MAILBOX_ID'], $syncLock - $this->syncTimeout ); if (!$DB->query($lockSql)->affectedRowsCount()) { return; } $outgoingBody = $excerpt['__BODY_HTML']; $excerpt['__files'] = Mail\Internals\MailMessageAttachmentTable::getList(array( 'select' => array( 'ID', 'FILE_ID', 'FILE_NAME', ), 'filter' => array( '=MESSAGE_ID' => $excerpt['__ID'], ), ))->fetchAll(); $attachments = array(); if (!empty($excerpt['__files']) && is_array($excerpt['__files'])) { $hostname = \COption::getOptionString('main', 'server_name', 'localhost'); if (defined('BX24_HOST_NAME') && BX24_HOST_NAME != '') { $hostname = BX24_HOST_NAME; } else if (defined('SITE_SERVER_NAME') && SITE_SERVER_NAME != '') { $hostname = SITE_SERVER_NAME; } foreach ($excerpt['__files'] as $item) { $file = \CFile::makeFileArray($item['FILE_ID']); $contentId = sprintf( 'bxacid.%s@%s.mail', hash('crc32b', $file['external_id'].$file['size'].$file['name']), hash('crc32b', $hostname) ); $attachments[] = array( 'ID' => $contentId, 'NAME' => $item['FILE_NAME'], 'PATH' => $file['tmp_name'], 'CONTENT_TYPE' => $file['type'], ); $outgoingBody = preg_replace( sprintf('/aid:%u/i', $item['ID']), sprintf('cid:%s', $contentId), $outgoingBody ); } } foreach (array('FROM', 'REPLY_TO', 'TO', 'CC', 'BCC') as $field) { $field = sprintf('__FIELD_%s', $field); if (strlen($excerpt[$field]) == 255 && '' != $excerpt['__HEADER'] && empty($parsedHeader)) { $parsedHeader = \CMailMessage::parseHeader($excerpt['__HEADER'], LANG_CHARSET); $excerpt['__FIELD_FROM'] = $parsedHeader->getHeader('FROM'); $excerpt['__FIELD_REPLY_TO'] = $parsedHeader->getHeader('REPLY-TO'); $excerpt['__FIELD_TO'] = $parsedHeader->getHeader('TO'); $excerpt['__FIELD_CC'] = $parsedHeader->getHeader('CC'); $excerpt['__FIELD_BCC'] = join(', ', array_merge( (array) $parsedHeader->getHeader('X-Original-Rcpt-to'), (array) $parsedHeader->getHeader('BCC') )); } $excerpt[$field] = explode(',', $excerpt[$field]); foreach ($excerpt[$field] as $k => $item) { unset($excerpt[$field][$k]); $address = new Main\Mail\Address($item); if ($address->validate()) { if ($address->getName()) { $excerpt[$field][] = sprintf( '%s <%s>', sprintf('=?%s?B?%s?=', SITE_CHARSET, base64_encode($address->getName())), $address->getEmail() ); } else { $excerpt[$field][] = $address->getEmail(); } } } $excerpt[$field] = join(', ', $excerpt[$field]); } $outgoingParams = array( 'CHARSET' => LANG_CHARSET, 'CONTENT_TYPE' => 'html', 'ATTACHMENT' => $attachments, 'TO' => $excerpt['__FIELD_TO'], 'SUBJECT' => $excerpt['__SUBJECT'], 'BODY' => $outgoingBody, 'HEADER' => array( 'From' => $excerpt['__FIELD_FROM'], 'Reply-To' => $excerpt['__FIELD_REPLY_TO'], //'To' => $excerpt['__FIELD_TO'], 'Cc' => $excerpt['__FIELD_CC'], 'Bcc' => $excerpt['__FIELD_BCC'], //'Subject' => $excerpt['__SUBJECT'], 'Message-Id' => sprintf('<%s>', $excerpt['__MSG_ID']), 'In-Reply-To' => sprintf('<%s>', $excerpt['__IN_REPLY_TO']), 'X-Bitrix-Mail-Message-UID' => $excerpt['ID'], ), ); $context = new Main\Mail\Context(); $context->setCategory(Main\Mail\Context::CAT_EXTERNAL); $context->setPriority(Main\Mail\Context::PRIORITY_NORMAL); $eventManager = \Bitrix\Main\EventManager::getInstance(); $eventKey = $eventManager->addEventHandler( 'main', 'OnBeforeMailSend', function () use (&$excerpt) { if ($excerpt['UPLOAD_STAGE'] >= 2) { return new Main\EventResult(Main\EventResult::ERROR); } } ); $success = Main\Mail\Mail::send(array_merge( $outgoingParams, array( 'TRACK_READ' => array( 'MODULE_ID' => 'mail', 'FIELDS' => array('msgid' => $excerpt['__MSG_ID']), 'URL_PAGE' => '/pub/mail/read.php', ), //'TRACK_CLICK' => array( // 'MODULE_ID' => 'mail', // 'FIELDS' => array('msgid' => $excerpt['__MSG_ID']), //), 'CONTEXT' => $context, ) )); $eventManager->removeEventHandler('main', 'OnBeforeMailSend', $eventKey); if ($excerpt['UPLOAD_STAGE'] < 2 && !$success) { return false; } $needUpload = true; if ($context->getSmtp() && $context->getSmtp()->getFrom() == $this->mailbox['EMAIL']) { $needUpload = !in_array('deny_upload', (array) $this->mailbox['OPTIONS']['flags']); } if ($needUpload) { if ($excerpt['UPLOAD_STAGE'] < 2) { Mail\Internals\MessageUploadQueueTable::update( array( 'ID' => $excerpt['ID'], 'MAILBOX_ID' => $excerpt['MAILBOX_ID'], ), array( 'SYNC_STAGE' => 2, 'ATTEMPTS' => 1, ) ); } class_exists('Bitrix\Mail\Helper'); $message = new Mail\DummyMail(array_merge( $outgoingParams, array( 'HEADER' => array_merge( $outgoingParams['HEADER'], array( 'To' => $outgoingParams['TO'], 'Subject' => $outgoingParams['SUBJECT'], ) ), ) )); if ($this->uploadMessage($message, $excerpt)) { Mail\Internals\MessageUploadQueueTable::delete(array( 'ID' => $excerpt['ID'], 'MAILBOX_ID' => $excerpt['MAILBOX_ID'], )); } } else { Mail\Internals\MessageUploadQueueTable::update( array( 'ID' => $excerpt['ID'], 'MAILBOX_ID' => $excerpt['MAILBOX_ID'], ), array( 'SYNC_STAGE' => -1, 'SYNC_LOCK' => 0, ) ); } return; } public function resyncMessage(array &$excerpt) { $body = $this->downloadMessage($excerpt); if (!empty($body)) { return $this->cacheMessage( $body, array( 'replaces' => $excerpt['ID'], ) ); } return false; } public function downloadAttachments(array &$excerpt) { $body = $this->downloadMessage($excerpt); if (!empty($body)) { list(,,, $attachments) = \CMailMessage::parseMessage($body, $this->mailbox['LANG_CHARSET']); return $attachments; } return false; } public function isSupportLazyAttachments() { foreach ($this->getFilters() as $filter) { foreach ($filter['__actions'] as $action) { if (empty($action['LAZY_ATTACHMENTS'])) { return false; } } } return true; } public function getFilters($force = false) { if (is_null($this->filters) || $force) { $this->filters = Mail\MailFilterTable::getList(array( 'filter' => ORM\Query\Query::filter() ->where('ACTIVE', 'Y') ->where( ORM\Query\Query::filter()->logic('or') ->where('MAILBOX_ID', $this->mailbox['ID']) ->where('MAILBOX_ID', null) ), 'order' => array( 'SORT' => 'ASC', 'ID' => 'ASC', ), ))->fetchAll(); foreach ($this->filters as $k => $item) { $this->filters[$k]['__actions'] = array(); $res = \CMailFilter::getFilterList($item['ACTION_TYPE']); while ($row = $res->fetch()) { $this->filters[$k]['__actions'][] = $row; } } } return $this->filters; } /** * @deprecated */ public function resortTree($message = null) { global $DB; $worker = function ($id, $msgId, &$i) { global $DB; $stack = array( array( array($id, $msgId, false), ), ); $excerpt = array(); do { $level = array_pop($stack); while ($level) { list($id, $msgId, $skip) = array_shift($level); if (!$skip) { $excerpt[] = $id; $DB->query(sprintf( 'UPDATE b_mail_message SET LEFT_MARGIN = %2$u, RIGHT_MARGIN = %3$u WHERE ID = %1$u', $id, ++$i, ++$i )); if (!empty($msgId)) { $replies = array(); $res = Mail\MailMessageTable::getList(array( 'select' => array( 'ID', 'MSG_ID', ), 'filter' => array( '=MAILBOX_ID' => $this->mailbox['ID'], '=IN_REPLY_TO' => $msgId, ), 'order' => array( 'FIELD_DATE' => 'ASC', ), )); while ($item = $res->fetch()) { if (!in_array($item['ID'], $excerpt)) { $replies[] = array($item['ID'], $item['MSG_ID'], false); } } if ($replies) { array_unshift($level, array($id, $msgId, true)); array_push($stack, $level, $replies); $i--; continue 2; } } } else { $DB->query(sprintf( 'UPDATE b_mail_message SET RIGHT_MARGIN = %2$u WHERE ID = %1$u', $id, ++$i )); } } } while ($stack); }; if (!empty($message)) { if (empty($message['ID'])) { throw new Main\ArgumentException("Argument 'message' is not valid"); } $item = $DB->query(sprintf( 'SELECT GREATEST(M1, M2) AS I FROM (SELECT (SELECT RIGHT_MARGIN FROM b_mail_message WHERE MAILBOX_ID = %1$u AND RIGHT_MARGIN > 0 ORDER BY LEFT_MARGIN ASC LIMIT 1) M1, (SELECT RIGHT_MARGIN FROM b_mail_message WHERE MAILBOX_ID = %1$u AND RIGHT_MARGIN > 0 ORDER BY LEFT_MARGIN DESC LIMIT 1) M2 ) M', $this->mailbox['ID'] ))->fetch(); $i = empty($item['I']) ? 0 : $item['I']; $worker($message['ID'], $message['MSG_ID'], $i); } else { $DB->query(sprintf( 'UPDATE b_mail_message SET LEFT_MARGIN = 0, RIGHT_MARGIN = 0 WHERE MAILBOX_ID = %u', $this->mailbox['ID'] )); $i = 0; $res = $DB->query(sprintf( "SELECT ID, MSG_ID FROM b_mail_message M WHERE MAILBOX_ID = %u AND ( IN_REPLY_TO IS NULL OR IN_REPLY_TO = '' OR NOT EXISTS ( SELECT 1 FROM b_mail_message WHERE MAILBOX_ID = M.MAILBOX_ID AND MSG_ID = M.IN_REPLY_TO ) )", $this->mailbox['ID'] )); while ($item = $res->fetch()) { $worker($item['ID'], $item['MSG_ID'], $i); } // crosslinked messages $query = sprintf( 'SELECT ID, MSG_ID FROM b_mail_message WHERE MAILBOX_ID = %u AND LEFT_MARGIN = 0 ORDER BY FIELD_DATE ASC LIMIT 1', $this->mailbox['ID'] ); while ($item = $DB->query($query)->fetch()) { $worker($item['ID'], $item['MSG_ID'], $i); } } } /** * @deprecated */ public function incrementTree($message) { if (empty($message['ID'])) { throw new Main\ArgumentException("Argument 'message' is not valid"); } if (!empty($message['IN_REPLY_TO'])) { $item = Mail\MailMessageTable::getList(array( 'select' => array( 'ID', 'MSG_ID', 'LEFT_MARGIN', 'RIGHT_MARGIN', ), 'filter' => array( '=MAILBOX_ID' => $this->mailbox['ID'], '=MSG_ID' => $message['IN_REPLY_TO'], ), 'order' => array( 'LEFT_MARGIN' => 'ASC', ), ))->fetch(); if (!empty($item)) { $message = $item; $item = Mail\MailMessageTable::getList(array( 'select' => array( 'ID', 'MSG_ID', ), 'filter' => array( '=MAILBOX_ID' => $this->mailbox['ID'], '<LEFT_MARGIN' => $item['LEFT_MARGIN'], '>RIGHT_MARGIN' => $item['RIGHT_MARGIN'], ), 'order' => array( 'LEFT_MARGIN' => 'ASC', ), 'limit' => 1, ))->fetch(); if (!empty($item)) { $message = $item; } } } $this->resortTree($message); } abstract protected function syncInternal(); abstract public function uploadMessage(Main\Mail\Mail $message, array &$excerpt); abstract public function downloadMessage(array &$excerpt); public function getErrors() { return $this->errors; } public function getWarnings() { return $this->warnings; } public function getLastSyncResult() { return $this->lastSyncResult; } protected function setLastSyncResult(array $data) { $this->lastSyncResult = array_merge($this->lastSyncResult, $data); } } mailcontact.php 0000644 00000006234 14774470223 0007572 0 ustar 00 <?php namespace Bitrix\Mail\Helper; /** * Class MailContact * @package Bitrix\Mail\Helper */ class MailContact { const COLOR_GREEN = '#9dcf01'; const COLOR_BLUE = '#2fc6f6'; const COLOR_LIGHT_BLUE = '#56d1e0'; const COLOR_ORANGE = '#ffa900'; const COLOR_CYAN = '#47e4c2'; const COLOR_PINK = '#ff5b55'; const COLOR_PURPLE = '#9985dd'; const COLOR_GREY = '#a8adb4'; const COLOR_BROWN = '#af7e00'; const COLOR_RED = '#F44336'; const COLOR_DEEP_PURPLE = '#673AB7'; const COLOR_INDIGO = '#3F51B5'; const COLOR_TEAL = '#009688'; const COLOR_LIGHT_GREEN = '#8BC34A'; const COLOR_LIME = '#CDDC39'; const COLOR_YELLOW = '#FFEB3B'; const COLOR_AMBER = '#FFC107'; const COLOR_DEEP_ORANGE = '#FF5722'; const COLOR_BLUE_GREY = '#607D8B'; /** * @param $email * @param $name * @param null $lastName * @return array * @throws \ReflectionException */ public static function getIconData($email, $name, $lastName = null) { return [ 'INITIALS' => static::getInitials($email, $name, $lastName), 'COLOR' => static::getRandomColor(), ]; } /** * @return mixed * @throws \ReflectionException */ public static function getRandomColor() { static $colors = null; if (is_null($colors)) { $reflect = new \ReflectionClass(static::class); foreach ($reflect->getConstants() as $name => $value) { if (strncmp($name, 'COLOR', 5) === 0) { $colors[] = $value; } } } return $colors[rand(0, count($colors) - 1)]; } /** return two symbols from name and last name, or 1 - from name or email * @param $email * @param $name * @param null $lastName * @return string */ public static function getInitials($email, $name = null, $lastName = null) { if ($lastName && substr($lastName, 0, 1) && $name && substr($name, 0, 1)) { return strtoupper(substr($name, 0, 1) . substr($lastName, 0, 1)); } $name = explode(' ', $name); if (is_array($name) && isset($name[0]) && $name[0]) { if (isset($name[1]) && $name[1]) { return strtoupper(substr($name[0], 0, 1) . substr($name[1], 0, 1)); } else { return strtoupper(substr($name[0], 0, 1)); } } return strtoupper(substr($email, 0, 1)); } /** returns array of fields and their values for adding to database * @param $mailsField * @param $userId * @param $addedFrom * @return array * @throws \ReflectionException */ public static function getContactsData($mailsField, $userId, $addedFrom) { if (!$mailsField) { return []; } $mails = explode(',', $mailsField); $contacts = []; foreach ($mails as $mail) { $mail = trim($mail); $address = new \Bitrix\Main\Mail\Address($mail); $emailToAdd = $nameToAdd = ''; if ($address->validate()) { $emailToAdd = $address->getEmail(); $nameToAdd = trim($address->getName()); } if ($emailToAdd) { $contacts[] = [ 'USER_ID' => intval($userId), 'NAME' => $nameToAdd ? $nameToAdd : explode('@', $emailToAdd)[0], 'ICON' => static::getIconData($emailToAdd, $nameToAdd), 'EMAIL' => $emailToAdd, 'ADDED_FROM' => $addedFrom, ]; } } return $contacts; } } oauth.php 0000664 00000023722 14774470223 0006417 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Mail; abstract class OAuth { protected $oauthEntity; protected $service, $storedUid; /** * Returns the list of supported services * * @return array */ public static function getKnownServices() { static $knownServices; if (is_null($knownServices)) { $knownServices = array( OAuth\Google::getServiceName(), OAuth\LiveId::getServiceName(), OAuth\Yandex::getServiceName(), OAuth\Mailru::getServiceName(), ); } return $knownServices; } /** * Returns helper instance * * @param string $service Service name. * @return \Bitrix\Mail\Helper\OAuth|false */ public static function getInstance($service = null) { if (get_called_class() != get_class()) { $className = get_called_class(); $service = $className::getServiceName(); } else { if (!in_array($service, self::getKnownServices())) { return false; } $className = sprintf('%s\OAuth\%s', __NAMESPACE__, $service); } if (!Main\Loader::includeModule('socialservices')) { return false; } $instance = new $className; $instance->service = $service; $instance->storedUid = sprintf('%x%x', time(), rand(0, 0xffffffff)); if (!$instance->check()) { return false; } return $instance; } /** * Returns helper instance by packed metadata * * @param string $meta Packed metadata. * @return \Bitrix\Mail\Helper\OAuth|false */ public static function getInstanceByMeta($meta) { if ($meta = static::parseMeta($meta)) { if ($instance = self::getInstance($meta['service'])) { if ('oauthb' == $meta['type']) { $instance->storedUid = $meta['key']; } return $instance; } } } /** * Returns packed metadata for instance * * @return string */ public function buildMeta() { return sprintf( "\x00oauthb\x00%s\x00%s", $this->getServiceName(), $this->getStoredUid() ); } /** * Returns unpacked metadata * * @param string $meta Packed metadata. * @return array */ public static function parseMeta($meta) { $regex = sprintf( '/^\x00(oauthb)\x00(%s)\x00([a-f0-9]+)$/', join( '|', array_map( function ($item) { return preg_quote($item, '/'); }, self::getKnownServices() ) ) ); if (!preg_match($regex, $meta, $matches)) { if (!preg_match('/^\x00(oauth)\x00(google|liveid)\x00(\d+)$/', $meta, $matches)) { return null; } } return array( 'type' => $matches[1], 'service' => $matches[2], 'key' => $matches[3], ); } /** * Returns token from socialservices * * @param string $service Service name. * @param string $key User ID. * @return string|null */ private static function getSocservToken($service, $key) { if (Main\Loader::includeModule('socialservices')) { switch ($service) { case 'google': $oauthClient = new \CSocServGoogleOAuth($key); $oauthClient->getUrl('modal', array('https://mail.google.com/')); break; case 'liveid': $oauthClient = new \CSocServLiveIDOAuth($key); $oauthClient->getUrl('modal', array('wl.imap', 'wl.offline_access')); break; } if (!empty($oauthClient)) { return $oauthClient->getStorageToken() ?: false; } } return null; } /** * Returns token by packed metadata * * @param string $meta Packed metadata. * @return string|null */ public static function getTokenByMeta($meta) { if ($meta = static::parseMeta($meta)) { if ('oauthb' == $meta['type']) { if ($oauthHelper = self::getInstance($meta['service'])) { return $oauthHelper->getStoredToken($meta['key']) ?: false; } } else if ('oauth' == $meta['type']) { return self::getSocservToken($meta['service'], $meta['key']); } } } /** * Returns user data by packed metadata * * @param string $meta Packed metadata. * @param boolean $secure Strip raw data (includes tokens). * @return array|null */ public static function getUserDataByMeta($meta, $secure = true) { if ($meta = static::parseMeta($meta)) { if ($oauthHelper = self::getInstance($meta['service'])) { if ('oauthb' == $meta['type']) { $oauthHelper->getStoredToken($meta['key']); } else if ('oauth' == $meta['type']) { if ($token = self::getSocservToken($meta['service'], $meta['key'])) { $oauthHelper->getOAuthEntity()->setToken($token); } } return $oauthHelper->getUserData($secure); } } return null; } /** * Returns service interface entity * * @return mixed */ public function getOAuthEntity() { return $this->oauthEntity; } /** * Returns instance UID * * @return string */ public function getStoredUid() { return $this->storedUid; } /** * Returns server OAuth handler URI * * @param boolean $final Skip Bitrix24 proxy. * @return string */ public function getRedirect($final = true) { return isModuleInstalled('bitrix24') && !$final ? $this->getControllerUrl() . '/redirect.php' : $this->getHostUrl() . '/bitrix/tools/mail_oauth.php'; } public function getHostUrl() { $request = Main\Context::getCurrent()->getRequest(); $uri = new Main\Web\Uri(sprintf( '%s://%s:%u', $request->isHttps() ? 'https' : 'http', $request->getHttpHost(), Main\Context::getCurrent()->getServer()->getServerPort() // $request->getServerPort() )); return rtrim($uri->getLocator(), '/'); } /** * Returns service OAuth endpoint URI * * @return string */ public function getUrl() { global $APPLICATION; \CSocServAuthManager::setUniqueKey(); if (isModuleInstalled('bitrix24')) { $state = sprintf( '%s?%s', $this->getRedirect(), http_build_query(array( 'check_key' => $_SESSION['UNIQUE_KEY'], 'dummy' => 'https://dummy.bitrix24.com/', 'state' => rawurlencode(http_build_query(array( 'service' => $this->service, 'uid' => $this->storedUid, ))), )) ); } else { $state = http_build_query(array( 'check_key' => $_SESSION['UNIQUE_KEY'], 'service' => $this->service, 'uid' => $this->storedUid, )); } return $this->oauthEntity->getAuthUrl($this->getRedirect(false), $state); } /** * Fetches token by instance UID from DB * * @return array|false */ protected function fetchStoredToken() { return Mail\Internals\OAuthTable::getList(array( 'filter' => array( '=UID' => $this->storedUid, ), 'order' => array( 'ID' => 'DESC', ), ))->fetch(); } /** * Returns token by instance UID * * @param string $uid Instance UID. * @return string|null */ public function getStoredToken($uid = null) { $token = null; if (!empty($uid)) { $this->storedUid = $uid; } $item = $this->fetchStoredToken(); if (!empty($item)) { $this->oauthEntity->setToken($token = $item['TOKEN']); $this->oauthEntity->setRefreshToken($item['REFRESH_TOKEN']); if (empty($token) || $item['TOKEN_EXPIRES'] > 0 && $item['TOKEN_EXPIRES'] < time()) { $this->oauthEntity->setToken(null); if (!empty($item['REFRESH_TOKEN'])) { if ($this->oauthEntity->getNewAccessToken($item['REFRESH_TOKEN'])) { $tokenData = $this->oauthEntity->getTokenData(); Mail\Internals\OAuthTable::update( $item['ID'], array( 'TOKEN' => $tokenData['access_token'], 'REFRESH_TOKEN' => $tokenData['refresh_token'], 'TOKEN_EXPIRES' => $tokenData['expires_in'], ) ); } } $token = $this->oauthEntity->getToken(); } } return $token; } /** * Obtains tokens from service * * @param string $code Service authorization code. * @return boolean */ public function getAccessToken($code = null) { if ($code) { $this->oauthEntity->setCode($code); } $oauthData = $_SESSION['OAUTH_DATA']; $result = $this->oauthEntity->getAccessToken($this->getRedirect(false)); $_SESSION['OAUTH_DATA'] = $oauthData; return $result; } /** * Returns user data * * @param boolean $secure Strip raw data (includes tokens). * @return array|null */ public function getUserData($secure = true) { try { $userData = $this->oauthEntity->getCurrentUser(); } catch (Main\SystemException $e) { } if (!empty($userData)) { return array_merge( $this->mapUserData($userData), $secure ? array() : array('__data' => $userData) ); } } /** * Returns unified user data * * @param array $userData User data. * @return array */ abstract protected function mapUserData(array $userData); /** * Returns service name * * @throws \Bitrix\Main\ObjectException * @return void */ public static function getServiceName() { throw new Main\ObjectException('abstract'); } /** * Handles service response * * @param string $state Response data. * @return void */ public function handleResponse($state) { $this->storedUid = $state['uid']; if ($item = $this->fetchStoredToken()) { $this->oauthEntity->setRefreshToken($item['REFRESH_TOKEN']); } if (!empty($_REQUEST['code']) && \CSocServAuthManager::checkUniqueKey()) { $this->getAccessToken($_REQUEST['code']); if ($userData = $this->getUserData(false)) { $fields = array( 'UID' => $this->getStoredUid(), 'TOKEN' => $userData['__data']['access_token'], 'REFRESH_TOKEN' => $userData['__data']['refresh_token'], 'TOKEN_EXPIRES' => $userData['__data']['expires_in'], ); if (empty($item)) { Mail\Internals\OAuthTable::add($fields); } else { Mail\Internals\OAuthTable::update($item['ID'], $fields); } unset($userData['__data']); ?> <script type="text/javascript"> targetWindow = window.opener ? window.opener : window; targetWindow.BX.onCustomEvent( 'OnMailOAuthBCompleted', [ '<?=\CUtil::jsEscape($this->getStoredUid()) ?>', '<?=\CUtil::jsEscape($this->getUrl()) ?>', <?=Main\Web\Json::encode($userData) ?> ] ); if (targetWindow !== window) { window.close(); } </script> <? } } } } messageindexstepper.php 0000664 00000002157 14774470223 0011355 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Mail; class MessageIndexStepper extends Main\Update\Stepper { const INDEX_VERSION = 1; protected static $moduleId = 'mail'; public function execute(array &$option) { $option['steps'] = Mail\MailMessageTable::getCount(array( '=INDEX_VERSION' => static::INDEX_VERSION, )); $option['count'] = Mail\MailMessageTable::getCount(array( '<=INDEX_VERSION' => static::INDEX_VERSION, )); if ($option['steps'] >= $option['count']) { return false; } $res = Mail\MailMessageTable::getList(array( 'select' => array( 'ID', 'FIELD_FROM', 'FIELD_REPLY_TO', 'FIELD_TO', 'FIELD_CC', 'FIELD_BCC', 'SUBJECT', 'BODY', ), 'filter' => array( '<INDEX_VERSION' => static::INDEX_VERSION, ), 'order' => array('ID' => 'ASC'), 'limit' => 1000, )); while ($item = $res->fetch()) { $option['steps']++; $fields = array( 'SEARCH_CONTENT' => Message::prepareSearchContent($item), 'INDEX_VERSION' => static::INDEX_VERSION, ); Mail\MailMessageTable::update($item['ID'], $fields); } return true; } } messageclosurestepper.php 0000664 00000006401 14774470223 0011716 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Mail; class MessageClosureStepper extends Main\Update\Stepper { protected static $moduleId = 'mail'; public function execute(array &$option) { global $DB, $pPERIOD; $pPERIOD = 10; $option['count'] = Mail\MailMessageTable::getCount(); $option['steps'] = Mail\Internals\MessageClosureTable::getList(array( 'select' => array( new Main\Entity\ExpressionField('CNT', 'COUNT(DISTINCT %s)', 'MESSAGE_ID') ), ))->fetch()['CNT']; if ($option['steps'] < $option['count']) { if (!($option['mailboxId'] > 0) || $option['stage'] < 1) { $option['mailboxId'] = $DB->query( 'SELECT MAILBOX_ID FROM b_mail_message M WHERE NOT EXISTS ( SELECT 1 FROM b_mail_message_closure WHERE MESSAGE_ID = M.ID ) LIMIT 1' )->fetch()['MAILBOX_ID']; $option['stage'] = 1; } } else { $option['mailboxId'] = false; $option['stage'] = 3; } if ($option['mailboxId'] > 0 && 1 == $option['stage']) { $res = $DB->query(sprintf( 'INSERT IGNORE INTO b_mail_message_closure (MESSAGE_ID, PARENT_ID) ( SELECT M.ID, M.ID FROM b_mail_message M WHERE M.MAILBOX_ID = %u AND ( M.IN_REPLY_TO IS NULL OR M.IN_REPLY_TO = "" OR M.IN_REPLY_TO = M.MSG_ID OR NOT EXISTS ( SELECT 1 FROM b_mail_message WHERE MAILBOX_ID = M.MAILBOX_ID AND MSG_ID = M.IN_REPLY_TO ) ) AND NOT EXISTS (SELECT 1 FROM b_mail_message_closure WHERE MESSAGE_ID = M.ID) LIMIT 40000 )', $option['mailboxId'] ))->affectedRowsCount(); $option['stage'] = $res < 40000 ? 2 : 1; $option['steps'] += $res; return self::CONTINUE_EXECUTION; } if ($option['mailboxId'] > 0 && 2 == $option['stage']) { $res = $DB->query(sprintf( 'INSERT IGNORE INTO b_mail_message_closure (MESSAGE_ID, PARENT_ID) ( SELECT DISTINCT M.ID, C.PARENT_ID FROM b_mail_message M LEFT JOIN b_mail_message R ON M.MAILBOX_ID = R.MAILBOX_ID AND M.IN_REPLY_TO = R.MSG_ID LEFT JOIN b_mail_message_closure C ON R.ID = C.MESSAGE_ID WHERE M.MAILBOX_ID = %u AND EXISTS (SELECT 1 FROM b_mail_message_closure WHERE MESSAGE_ID = R.ID) AND NOT EXISTS (SELECT 1 FROM b_mail_message_closure WHERE MESSAGE_ID = M.ID) )', $option['mailboxId'] ))->affectedRowsCount(); $option['stage'] = $res > 0 ? 3 : 4; return self::CONTINUE_EXECUTION; } if (3 == $option['stage']) { $res = $DB->query( 'INSERT IGNORE INTO b_mail_message_closure (MESSAGE_ID, PARENT_ID) ( SELECT DISTINCT C.MESSAGE_ID, C.MESSAGE_ID FROM b_mail_message_closure C WHERE NOT EXISTS (SELECT 1 FROM b_mail_message_closure WHERE PARENT_ID = C.MESSAGE_ID) )' )->affectedRowsCount(); $option['stage'] = $res > 0 ? 2 : 4; } if (4 == $option['stage']) { $res = $DB->query(sprintf( 'INSERT IGNORE INTO b_mail_message_closure (MESSAGE_ID, PARENT_ID) ( SELECT M.ID, M.ID FROM b_mail_message M WHERE M.MAILBOX_ID = %u AND NOT EXISTS (SELECT 1 FROM b_mail_message_closure WHERE MESSAGE_ID = M.ID) ORDER BY FIELD_DATE ASC LIMIT 1 )', $option['mailboxId'] ))->affectedRowsCount(); $option['stage'] = $res > 0 ? 2 : 0; } return $option['mailboxId'] > 0 ? self::CONTINUE_EXECUTION : self::FINISH_EXECUTION; } } licensemanager.php 0000664 00000002637 14774470223 0010256 0 ustar 00 <?php namespace Bitrix\Mail\Helper; use Bitrix\Main; use Bitrix\Bitrix24; /** * Class LicenseManager */ class LicenseManager { /** * Checks if mailboxes synchronization is available * * @return bool */ public static function isSyncAvailable() { if (!Main\Loader::includeModule('bitrix24')) { return true; } return (bool) Bitrix24\Feature::isFeatureEnabled('mail_mailbox_sync'); } public static function getSharedMailboxesLimit() { if (!Main\Loader::includeModule('bitrix24')) { return -1; } return (int) Bitrix24\Feature::getVariable('mail_shared_mailboxes_limit'); } /** * How many mailboxes a user can connect * * @return int */ public static function getUserMailboxesLimit() { if (!Main\Loader::includeModule('bitrix24')) { return -1; } if (!static::isSyncAvailable()) { return 0; } return (int) Bitrix24\Feature::getVariable('mail_user_mailboxes_limit'); } /** * Returns the number of days to store messages * * @return int */ public static function getSyncOldLimit() { if (!Main\Loader::includeModule('bitrix24')) { return (int) Main\Config\Option::get('mail', 'sync_old_limit2', -1); } return (int) Bitrix24\Feature::getVariable('mail_sync_old_limit'); } /** * Checks if old messages should be deleted * * @return bool */ public static function isCleanupOldEnabled() { return static::getSyncOldLimit() > 0; } } attachment/storage.php 0000664 00000007340 14774470223 0011071 0 ustar 00 <?php namespace Bitrix\Mail\Helper\Attachment; use Bitrix\Main; use Bitrix\Main\Localization\Loc; use Bitrix\Mail; class Storage { /** * Returns attachments disk storage * * @return \Bitrix\Disk\Storage|false */ public static function getStorage() { static $storage; if (!is_null($storage)) { return $storage; } $storage = false; if (!Main\Loader::includeModule('disk')) { return $storage; } $storageId = Main\Config\Option::get('mail', 'disk_attachment_storage_id', 0); if ($storageId > 0) { $storage = \Bitrix\Disk\Storage::loadById($storageId); if (!$storage || $storage->getModuleId() != 'mail') { $storage = false; } } if (!$storage) { $driver = \Bitrix\Disk\Driver::getInstance(); $storage = $driver->addStorageIfNotExist(array( 'NAME' => Loc::getMessage('MAIL_ATTACHMENT_STORAGE_NAME'), 'USE_INTERNAL_RIGHTS' => false, 'MODULE_ID' => 'mail', 'ENTITY_TYPE' => Mail\Disk\ProxyType\Mail::className(), 'ENTITY_ID' => 'mail', )); if ($storage) { Main\Config\Option::set('mail', 'disk_attachment_storage_id', $storage->getId()); } else { $storage = false; } } return $storage; } /** * Returns disk url manager * * @return \Bitrix\Disk\UrlManager|false */ public static function getUrlManager() { static $urlManager; if (!is_null($urlManager)) { return $urlManager; } $urlManager = false; if (!Main\Loader::includeModule('disk')) { return $urlManager; } $urlManager = \Bitrix\Disk\Driver::getInstance()->getUrlManager(); return $urlManager; } /** * Returns disk objects by file ID * * @param int $fileId File ID. * @param int $limit Limit. * @return array */ public static function getObjectsByFileId($fileId, $limit = 0) { $storage = static::getStorage(); if (!$storage) { return array(); } return \Bitrix\Disk\File::getModelList(array( 'filter' => array( '=STORAGE_ID' => $storage->getId(), '=TYPE' => \Bitrix\Disk\Internals\ObjectTable::TYPE_FILE, '=FILE_ID' => $fileId, ), 'limit' => $limit, )); } /** * Returns disk object by attachment file data (creates one if not exists) * * @param array $attachment Attachment file data. * @param boolean $create Create object if not exists. * @return \Bitrix\Disk\File|false|null */ public static function getObjectByAttachment(array $attachment, $create = false) { $object = reset(static::getObjectsByFileId($attachment['FILE_ID'], 1)); if (empty($object) && $create) { $object = static::registerAttachment($attachment); } return $object; } /** * Creates disk object for attachment file * * @param array $attachment Attachment file data. * @return \Bitrix\Disk\File|false|null */ public static function registerAttachment(array $attachment) { $storage = static::getStorage(); if (!$storage) { return false; } $folder = $storage->getChild(array( '=NAME' => date('Y-m'), '=TYPE' => \Bitrix\Disk\Internals\FolderTable::TYPE, )); if (!$folder) { $folder = $storage->addFolder(array( 'NAME' => date('Y-m'), 'CREATED_BY' => 1, // @TODO )); } if (!$folder) { $folder = $storage; } return $folder->addFile( array( 'NAME' => \Bitrix\Disk\Ui\Text::correctFilename($attachment['FILE_NAME']) ?: sprintf('%x', rand(0, 0xffffff)), 'FILE_ID' => $attachment['FILE_ID'], 'SIZE' => $attachment['FILE_SIZE'], 'CREATED_BY' => 1, // @TODO ), array(), true ); } /** * Deletes disk objects by file ID * * @param int $fileId File ID. * @return void */ public static function unregisterAttachment($fileId) { foreach (static::getObjectsByFileId($fileId) as $item) { $item->delete(1); // @TODO } } }