29 if(isset(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[$num]))
31 return self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[$num];
35 return self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[count(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS)-1];
45 $this->client =
new Mail\Imap(
59 if (!empty($this->syncParams[
'currentDir']))
61 $currentDir = $this->syncParams[
'currentDir'];
64 $totalSyncDirs = count($this->
getDirsHelper()->getSyncDirs());
66 $currentSyncDir = $this->
getDirsHelper()->getDirByPath($currentSyncDirPath);
68 if ($totalSyncDirs > 0 && $currentSyncDir !=
null)
70 $currentSyncDirMessages = Mail\MailMessageUidTable::getList([
75 '=MAILBOX_ID' => $this->mailbox[
'ID'],
76 '=DIR_MD5' => $currentSyncDir->getDirMd5(),
81 $currentSyncDirMessagesCount = (int)$currentSyncDirMessages[
'TOTAL'];
82 $currentSyncDirMessagesAll = (int)$currentSyncDir->getMessageCount();
83 $currentSyncDirPosition = $this->
getDirsHelper()->getCurrentSyncDirPositionByDefault(
84 $currentSyncDir->getPath(),
88 if ($currentDir !=
null) {
92 if ($currentSyncDirMessagesAll <= 0)
94 $progress = ($currentSyncDirPosition + 1) / $totalSyncDirs;
98 $progress = ($currentSyncDirMessagesCount / $currentSyncDirMessagesAll + $currentSyncDirPosition) / $totalSyncDirs;
105 return parent::getSyncStatus();
111 if (!empty($this->syncParams[
'currentDir']))
113 $currentSyncDir = $this->
getDirsHelper()->getDirByPath($this->syncParams[
'currentDir']);
116 if (!empty($currentSyncDir))
118 $currentSyncDirMessages = Mail\MailMessageUidTable::getList([
123 '=MAILBOX_ID' => $this->mailbox[
'ID'],
124 '=DIR_MD5' => $currentSyncDir->getDirMd5(),
125 '==DELETE_TIME' => 0,
129 $currentSyncDirMessagesCount = (int) $currentSyncDirMessages[
'TOTAL'];
130 $currentSyncDirMessagesAll = (int) $currentSyncDir->getMessageCount();
132 if ($currentSyncDirMessagesAll > 0)
134 return ($currentSyncDirMessagesCount / $currentSyncDirMessagesAll);
154 $chunks = array_chunk($UIDs, 5);
156 $existingMessage = NULL;
158 foreach ($chunks as $chunk)
160 $messages = $this->client->fetch(
169 if(!($messages ===
false || empty($messages)))
171 foreach ($messages as $item)
173 if(!isset($item[
'FLAGS']))
178 $messageDeleted = preg_grep(
'/^ \x5c Deleted $/ix', $item[
'FLAGS']) ? true :
false;
182 $existingMessage = $item;
189 if(!is_null($existingMessage))
191 if(isset($existingMessage[
'UID']))
193 return $existingMessage[
'UID'];
203 $mailboxID = $this->mailbox[
'ID'];
205 $syncDirs = $directoryHelper->getSyncDirs();
207 $numberOfUnSynchronizedDirs = count($syncDirs);
209 foreach ($syncDirs as $dir)
211 $dirPath = $dir->getPath();
212 $dirId = $dir->getId();
214 $internalDate = \Bitrix\Mail\Helper::getLastDeletedOldMessageInternaldate($mailboxID, $dirPath);
217 'MAILBOX_ID' => $mailboxID,
218 'ENTITY_TYPE' =>
'DIR',
219 'ENTITY_ID' => $dirId,
220 'PROPERTY_NAME' =>
'SYNC_IS_OLD_STATUS',
224 '=MAILBOX_ID' => $keyRow[
'MAILBOX_ID'],
225 '=ENTITY_TYPE' => $keyRow[
'ENTITY_TYPE'],
226 '=ENTITY_ID' => $keyRow[
'ENTITY_ID'],
227 '=PROPERTY_NAME' => $keyRow[
'PROPERTY_NAME'],
230 $startValue =
'started_for_date_'.$internalDate;
232 if(
Mail\Internals\MailEntityOptionsTable::getCount($filter))
234 if(
Mail\Internals\MailEntityOptionsTable::getList([
239 ])->fetchAll()[0][
'VALUE'] !==
'completed')
241 Mail\Internals\MailEntityOptionsTable::update(
243 [
'VALUE' => $startValue]
248 if($synchronizationSuccess)
250 Mail\Internals\MailEntityOptionsTable::update(
252 [
'VALUE' =>
'completed']
254 $numberOfUnSynchronizedDirs--;
259 $numberOfUnSynchronizedDirs--;
265 $fields[
'VALUE'] = $startValue;
266 Mail\Internals\MailEntityOptionsTable::add(
272 if($synchronizationSuccess)
274 \Bitrix\Mail\Internals\MailEntityOptionsTable::update(
276 [
'VALUE' =>
'completed']
278 $numberOfUnSynchronizedDirs--;
283 if($numberOfUnSynchronizedDirs === 0)
295 $mailboxID = $this->mailbox[
'ID'];
297 $syncDirs = $directoryHelper->getSyncDirs();
299 $numberOfUnSynchronizedDirs = count($syncDirs);
301 foreach ($syncDirs as $dir)
303 $dirPath = $dir->getPath();
304 $dirId = $dir->getId();
306 $internalDate = \Bitrix\Mail\Helper::getStartInternalDateForDir($mailboxID,$dirPath);
309 'MAILBOX_ID' => $mailboxID,
310 'ENTITY_TYPE' =>
'DIR',
311 'ENTITY_ID' => $dirId,
312 'PROPERTY_NAME' =>
'SYNC_FIRST_DAY',
316 '=MAILBOX_ID' => $keyRow[
'MAILBOX_ID'],
317 '=ENTITY_TYPE' => $keyRow[
'ENTITY_TYPE'],
318 '=ENTITY_ID' => $keyRow[
'ENTITY_ID'],
319 '=PROPERTY_NAME' => $keyRow[
'PROPERTY_NAME'],
322 $startValue =
'started_for_date_'.$internalDate;
324 if(
Mail\Internals\MailEntityOptionsTable::getCount($filter))
326 if(
Mail\Internals\MailEntityOptionsTable::getList([
331 ])->fetchAll()[0][
'VALUE'] !==
'completed')
333 Mail\Internals\MailEntityOptionsTable::update(
335 [
'VALUE' => $startValue]
338 \CTimeZone::Disable();
340 \CTimeZone::Enable();
342 if($synchronizationSuccess)
344 Mail\Internals\MailEntityOptionsTable::update(
346 [
'VALUE' =>
'completed']
348 $numberOfUnSynchronizedDirs--;
353 $numberOfUnSynchronizedDirs--;
359 $fields[
'VALUE'] = $startValue;
360 Mail\Internals\MailEntityOptionsTable::add(
364 \CTimeZone::Disable();
366 \CTimeZone::Enable();
368 if($synchronizationSuccess)
370 \Bitrix\Mail\Internals\MailEntityOptionsTable::update(
372 [
'VALUE' =>
'completed']
374 $numberOfUnSynchronizedDirs--;
379 if($numberOfUnSynchronizedDirs === 0)
392 if (
false === $syncReport[
'syncCount'])
402 $dirPath = $this->
getDirsHelper()->getOutcomePath() ?:
'INBOX';
404 $fields = array_merge(
407 'DIR_MD5' => md5($dirPath),
413 return parent::createMessage($message, $fields);
420 parent::syncOutgoing();
425 $dirPath = $this->
getDirsHelper()->getOutcomePath() ?:
'INBOX';
427 $data = $this->client->select($dirPath, $error);
436 if (!empty($excerpt[
'__unique_headers']))
438 if ($this->client->searchByHeader(
false, $dirPath, $excerpt[
'__unique_headers'], $error))
444 if (!empty($excerpt[
'ID']))
446 class_exists(
'Bitrix\Mail\Helper');
448 Mail\DummyMail::overwriteMessageHeaders(
451 'X-Bitrix-Mail-Message-UID' => $excerpt[
'ID'],
456 $result = $this->client->append(
462 $message->getHeaders(),
464 $message->getMailEol()
469 if (
false === $result)
483 if (empty($excerpt[
'MSG_UID']) || empty($excerpt[
'DIR_MD5']))
488 $dirPath = $this->
getDirsHelper()->getDirPathByHash($excerpt[
'DIR_MD5']);
494 $body = $this->client->fetch(
true, $dirPath, $excerpt[
'MSG_UID'],
'(BODY.PEEK[])', $error);
503 return empty($body[
'BODY[]']) ? null : $body[
'BODY[]'];
508 if (empty($excerpt[
'MSG_UID']) || empty($excerpt[
'DIR_MD5']))
513 $dirPath = $this->
getDirsHelper()->getDirPathByHash($excerpt[
'DIR_MD5']);
519 $rfc822Parts = array();
521 $select = array_filter(
522 $bodystructure->traverse(
523 function (
Mail\
Imap\BodyStructure $item) use ($flags, &$rfc822Parts)
525 if ($item->isMultipart())
530 $isTextItem = $item->isBodyText();
531 if ($flags & ($isTextItem ? Imap::MESSAGE_PARTS_TEXT : Imap::MESSAGE_PARTS_ATTACHMENT))
534 if (
'message' === $item->getType() &&
'rfc822' === $item->getSubtype())
536 $rfc822Parts[] = $item;
538 return sprintf(
'BODY.PEEK[%1$s.HEADER] BODY.PEEK[%1$s.TEXT]', $item->getNumber());
541 return sprintf(
'BODY.PEEK[%1$s.MIME] BODY.PEEK[%1$s]', $item->getNumber());
553 $parts = $this->client->fetch(
557 sprintf(
'(%s)', join(
' ', $select)),
561 if (
false === $parts)
568 foreach ($rfc822Parts as $item)
570 $headerKey = sprintf(
'BODY[%s.HEADER]', $item->getNumber());
571 $bodyKey = sprintf(
'BODY[%s.TEXT]', $item->getNumber());
573 if (array_key_exists($headerKey, $parts) || array_key_exists($bodyKey, $parts))
575 $partMime =
'Content-Type: message/rfc822';
576 if (!empty($item->getParams()[
'name']))
578 $partMime .= sprintf(
'; name="%s"', $item->getParams()[
'name']);
581 if (!empty($item->getDisposition()[0]))
583 $partMime .= sprintf(
"\r\nContent-Disposition: %s", $item->getDisposition()[0]);
584 if (!empty($item->getDisposition()[1]) && is_array($item->getDisposition()[1]))
586 foreach ($item->getDisposition()[1] as $name => $value)
588 $partMime .= sprintf(
'; %s="%s"', $name, $value);
593 $parts[sprintf(
'BODY[%1$s.MIME]', $item->getNumber())] = $partMime;
594 $parts[sprintf(
'BODY[%1$s]', $item->getNumber())] = sprintf(
596 rtrim($parts[$headerKey],
"\r\n"),
597 ltrim($parts[$bodyKey],
"\r\n")
600 unset($parts[$headerKey], $parts[$bodyKey]);
609 static $lastCacheSession;
611 if ($this->session === $lastCacheSession)
616 $dirs = $this->client->listex(
'',
'%', $error);
625 foreach ($dirs as $item)
627 $parts = explode($item[
'delim'], $item[
'name']);
629 $item[
'path'] = $item[
'name'];
630 $item[
'name'] = end($parts);
632 $list[$item[
'name']] = $item;
637 $lastCacheSession = $this->session;
642 $dirs = $this->client->listex(
'', $pattern, $error);
652 foreach ($dirs as $dir)
654 $parts = explode($dir[
'delim'], $dir[
'name']);
656 $dir[
'path'] = $dir[
'name'];
657 $dir[
'name'] = end($parts);
658 $list[$dir[
'path']] = $dir;
671 if (isset($messages[
'MSG_UID']))
673 $messages = [$messages];
677 foreach ($messages as $message)
679 $id = $message[
'MSG_UID'];
680 $folderFrom = $this->
getDirsHelper()->getDirPathByHash($message[
'DIR_MD5']);
681 $data[$folderFrom][] = $id;
682 $results[$folderFrom][] = $message;
684 return $result->setData($data);
690 foreach ($result->getData() as $folderFrom => $ids)
692 $result = $this->client->unseen($ids, $folderFrom);
693 if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
704 foreach ($result->getData() as $folderFrom => $ids)
706 $result = $this->client->seen($ids, $folderFrom);
707 if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
719 foreach ($result->getData() as $folderFrom => $ids)
721 $moveResult = $this->client->moveMails($ids, $folderFrom, $folderTo);
722 if (!$moveResult->isSuccess() || !$this->client->getErrors()->isEmpty())
735 foreach ($result->getData() as $folderName => $messageId)
737 $result = $this->client->delete($messageId, $folderName);
745 if (!$this->client->authenticate($error))
753 'reSyncStatus' =>
false,
760 if (!empty($this->syncParams[
'currentDir']))
762 $currentDir = $this->syncParams[
'currentDir'];
765 $dirsSync = $this->
getDirsHelper()->getSyncDirsOrderByTime($currentDir);
767 if (empty($dirsSync))
772 $lastDir = $this->
getDirsHelper()->getLastSyncDirByDefault($currentDir);
774 foreach ($dirsSync as $item)
778 $syncReport[
'syncCount'] += $this->
syncDir($item->getPath());
787 if ($lastDir !=
null && $item->getPath() == $lastDir->getPath())
807 '!@DIR_MD5' => array_map(
813 'info' =>
'disabled directory synchronization in Bitrix',
817 $countDeleted = $result ? $result->getCount() : 0;
819 $this->lastSyncResult[
'deletedMessages'] += $countDeleted;
821 $successfulReSyncCount = 0;
823 if (!empty($this->syncParams[
'full']))
825 foreach ($dirsSync as $item)
827 $reSyncReport = $this->
resyncDir($item->getPath());
829 if($reSyncReport[
'complete'])
831 $syncReport[
'reSyncCount']++;
839 if($syncReport[
'reSyncCount'] === count($dirsSync))
841 $syncReport[
'reSyncStatus'] =
true;
853 if (!$dir || !$dir->isSync())
858 if ($dir->isSyncLock() || !$dir->startSyncLock())
865 $dir->stopSyncLock();
867 $this->lastSyncResult[
'newMessages'] += $result;
868 if (!$dir->isTrash() && !$dir->isSpam() && !$dir->isDraft() && !$dir->isOutcome())
870 $this->lastSyncResult[
'newMessagesNotify'] += $result;
878 if($internalDate ===
false)
886 $entity = \Bitrix\Mail\MailMessageUidTable::getEntity();
887 $connection = $entity->getConnection();
891 Main\
Entity\Query::buildFilterSql(
894 '<=INTERNALDATE' => $internalDate,
895 '=DIR_MD5' => $dir->getDirMd5(),
896 '=MAILBOX_ID' => $mailboxId,
902 $connection->query(sprintf(
903 'UPDATE %s SET IS_OLD = "Y", IS_SEEN = "Y" WHERE %s LIMIT 1000',
904 $connection->getSqlHelper()->quote($entity->getDbTableName()),
908 if($connection->getAffectedRowsCount() === 0)
928 $meta = $this->client->select($dirPath, $error);
929 $uidtoken = $meta[
'uidvalidity'];
941 $chunks = array_chunk($UIDs, 10);
943 $entity = Mail\MailMessageUidTable::getEntity();
944 $connection = $entity->getConnection();
946 foreach ($chunks as $chunk)
948 $connection->query(sprintf(
949 'DELETE FROM %s WHERE %s',
951 Main\
Entity\Query::buildFilterSql(
954 '@MSG_UID' => $chunk,
956 '=MAILBOX_ID' => $mailboxID,
957 '=DIR_MD5' => $dir->getDirMd5()
962 $messages = $this->client->fetch(
966 '(UID FLAGS INTERNALDATE RFC822.SIZE BODYSTRUCTURE BODY.PEEK[HEADER])',
971 if (empty($messages))
973 if (
false === $messages)
975 $this->warnings->add($this->client->getErrors()->toArray());
983 $this->blacklistMessages($dir->getPath(), $messages);
987 foreach ($messages as &$message)
994 foreach ($messages as $item)
998 if(empty($item[
'__replaces']))
1002 if($outgoingMessageId)
1004 $item[
'__replaces'] = $outgoingMessageId;
1010 $this->
syncMessage($dir->getPath(), $item, $hashesMap,
true, $isOutgoing);
1024 if (\
Bitrix\
Mail\Helper::getImapUnseen($this->mailbox,
'inbox') ===
false)
1034 if($internalDate ===
false)
1039 $mailboxID = $this->mailbox[
'ID'];
1041 $UIDsOnService = \Bitrix\Mail\Helper::getImapUIDsForSpecificDay($mailboxID, $dirPath, $internalDate);
1043 return $this->
syncMessages($mailboxID, $dirPath, $UIDsOnService);
1048 $messagesSynced = 0;
1050 $meta = $this->client->select($dir->getPath(), $error);
1052 if (
false === $meta)
1054 $this->warnings->add($this->client->getErrors()->toArray());
1056 if ($this->client->isExistsDir($dir->getPath(), $error) ===
false)
1064 $this->
getDirsHelper()->updateMessageCount($dir->getId(), $meta[
'exists']);
1066 $intervalSynchronizationAttempts = 0;
1068 while ($range = $this->
getSyncRange($dir->getPath(), $uidtoken, $intervalSynchronizationAttempts))
1070 $reverse = $range[0] > $range[1];
1074 $messages = $this->client->fetch(
1078 '(UID FLAGS INTERNALDATE RFC822.SIZE BODYSTRUCTURE BODY.PEEK[HEADER])',
1082 $fetchErrors=$this->client->getErrors();
1083 $errorReceivingMessages = $fetchErrors->getErrorByCode(210) !==
null;
1084 $failureDueToDataVolume = $fetchErrors->getErrorByCode(104) !==
null;
1086 if (empty($messages))
1088 if (
false === $messages)
1090 if($errorReceivingMessages && !$failureDueToDataVolume)
1095 return $messagesSynced;
1097 elseif($failureDueToDataVolume && $intervalSynchronizationAttempts < count(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS) - 1 )
1099 $intervalSynchronizationAttempts++;
1110 $this->warnings->add($fetchErrors->toArray());
1118 $intervalSynchronizationAttempts = 0;
1121 $reverse ? krsort($messages) : ksort($messages);
1125 $this->blacklistMessages($dir->getPath(), $messages);
1129 foreach ($messages as &$message)
1139 $numberOfMessagesInABatch = 1;
1140 $numberLeftToFillTheBatch = $numberOfMessagesInABatch;
1142 foreach ($messages as $item)
1144 $isOutgoing =
false;
1146 if(empty($item[
'__replaces']))
1150 if($outgoingMessageId)
1152 $item[
'__replaces'] = $outgoingMessageId;
1157 if ($this->
syncMessage($dir->getPath(), $item, $hashesMap,
false, $isOutgoing))
1159 $this->lastSyncResult[
'newMessageId'] = end($hashesMap);
1162 $numberLeftToFillTheBatch--;
1163 if($numberLeftToFillTheBatch === 0 and Main\Loader::includeModule(
'pull'))
1165 $numberOfMessagesInABatch *= 2;
1166 $numberLeftToFillTheBatch = $numberOfMessagesInABatch;
1167 \CPullWatch::addToStack(
1168 'mail_mailbox_' . $this->mailbox[
'ID'],
1171 'dir' => $dir->getPath(),
1172 'mailboxId' => $this->mailbox[
'ID'],
1174 'module_id' =>
'mail',
1175 'command' =>
'new_message_is_synchronized',
1178 \Bitrix\Pull\Event::send();
1189 if (
false === $range)
1191 $this->warnings->add($this->client->getErrors()->toArray());
1196 return $messagesSynced;
1199 public function resyncDir($dirPath, $numberForResync =
false)
1203 if (!$dir || !$dir->isSync())
1209 'complete' =>
false,
1210 'dir' => $dir->getPath(),
1211 'updated' => -$this->lastSyncResult[
'updatedMessages'],
1212 'deleted' => -$this->lastSyncResult[
'deletedMessages'],
1217 $report[
'updated'] += $this->lastSyncResult[
'updatedMessages'];
1218 $report[
'deleted'] += $this->lastSyncResult[
'deletedMessages'];
1220 if (
false === $result)
1222 $report[
'errors'] = $this->client->getErrors()->toArray();
1228 $report[
'errors'] = [
1229 'isTimeQuotaExceeded'
1234 $report[
'complete'] =
true;
1243 $meta = $this->client->select($dir->getPath(), $error);
1244 if (
false === $meta)
1246 $this->warnings->add($this->client->getErrors()->toArray());
1251 $uidtoken = $meta[
'uidvalidity'];
1253 if ($meta[
'exists'] > 0)
1259 '=DIR_MD5' => md5($dir->getPath(
true)),
1260 '<DIR_UIDV' => $uidtoken,
1263 'info' =>
'the directory has been deleted',
1267 $countDeleted = $result ? $result->getCount() : 0;
1269 $this->lastSyncResult[
'deletedMessages'] += $countDeleted;
1274 if ($this->client->ensureEmpty($dir->getPath(), $error))
1278 '=DIR_MD5' => md5($dir->getPath(
true)),
1281 'info' =>
'all messages in the directory have been deleted ',
1285 $countDeleted = $result ? $result->getCount() : 0;
1287 $this->lastSyncResult[
'deletedMessages'] += $countDeleted;
1293 $fetcher =
function ($range) use ($dir)
1295 $messages = $this->client->fetch(
false, $dir->getPath(), $range,
'(UID FLAGS)', $error);
1297 if (empty($messages))
1299 if (
false === $messages)
1301 $this->warnings->add($this->client->getErrors()->toArray());
1316 $messagesNumberInTheMailService = $meta[
'exists'];
1317 $messages = $fetcher(($messagesNumberInTheMailService > 10000 || $numberForResync !==
false) ? sprintf(
'1,%u', $messagesNumberInTheMailService) :
'1:*');
1319 if (empty($messages))
1321 return (
false === $messages ?
false :
null);
1326 reset($messages)[
'UID'],
1327 end($messages)[
'UID'],
1331 if($range[0]===$range[1] and $messagesNumberInTheMailService > 1)
1339 '=DIR_MD5' => md5($dir->getPath(
true)),
1343 '<MSG_UID' => $range[0],
1344 '>MSG_UID' => $range[1],
1348 'info' =>
'optimized deletion of non-existent messages',
1352 $countDeleted = $result ? $result->getCount() : 0;
1354 $this->lastSyncResult[
'deletedMessages'] += $countDeleted;
1357 if($numberForResync !==
false)
1359 $range1 = $meta[
'exists'];
1360 $range0 = max($range1 - ($numberForResync - 1), 1);
1361 $messages = $fetcher(sprintf(
'%u:%u', $range0, $range1));
1363 if (empty($messages))
1368 $this->
resyncMessages($dir->getPath(
true), $uidtoken, $messages);
1373 if (!($meta[
'exists'] > 10000))
1375 $this->
resyncMessages($dir->getPath(
true), $uidtoken, $messages);
1380 $range1 = $meta[
'exists'];
1383 $rangeSize = $range1 > 10000 ? 8000 : $range1;
1384 $range0 = max($range1 - $rangeSize, 1);
1386 $messages = $fetcher(sprintf(
'%u:%u', $range0, $range1));
1388 if (empty($messages))
1393 $this->
resyncMessages($dir->getPath(
true), $uidtoken, $messages);
1400 $range1 -= $rangeSize;
1406 foreach ($messages as $id => $item)
1408 $messages[$id][
'__header'] = \CMailMessage::parseHeader($item[
'BODY[HEADER]'], $this->mailbox[
'LANG_CHARSET']);
1409 $messages[$id][
'__from'] = array_unique(array_map(
1413 \CMailUtil::extractAllMailAddresses($messages[$id][
'__header']->getHeader(
'FROM')),
1414 \CMailUtil::extractAllMailAddresses($messages[$id][
'__header']->getHeader(
'REPLY-TO'))
1422 protected function blacklistMessages($dirPath, &$messages)
1424 $trashDir = $this->getDirsHelper()->getTrashPath();
1425 $spamDir = $this->getDirsHelper()->getSpamPath();
1427 $targetDir = $spamDir ?: $trashDir ?:
null;
1428 $dir = $this->getDirsHelper()->getDirByPath($dirPath);
1430 if (empty($targetDir) || ($dir && ($dir->isTrash() || $dir->isSpam())))
1437 'domain' => array(),
1440 $blacklistEmails = Mail\BlacklistTable::query()
1443 '=SITE_ID' => $this->mailbox[
'LID'],
1446 '=MAILBOX_ID' => $this->mailbox[
'ID'],
1449 '@USER_ID' => array(0, $this->mailbox[
'USER_ID']),
1454 ->fetchCollection();
1455 foreach ($blacklistEmails as $blacklistEmail)
1457 if ($blacklistEmail->isDomainType())
1459 $blacklist[
'domain'][] = $blacklistEmail;
1463 $blacklist[
'email'][] = $blacklistEmail;
1467 if (empty($blacklist[
'email']) && empty($blacklist[
'domain']))
1472 $targetMessages = [];
1473 $emailAddresses = array_map(
function ($element)
1476 return $element->getItemValue();
1477 }, $blacklist[
'email']);
1478 $domains = array_map(
function ($element)
1481 return $element->getItemValue();
1482 }, $blacklist[
'domain']);
1484 foreach ($messages as $id => $item)
1486 if (!empty($blacklist[
'email']))
1488 if (array_intersect($messages[$id][
'__from'], $emailAddresses))
1490 $targetMessages[$id] = $item[
'UID'];
1496 foreach ($blacklist[
'email'] as $blacklistMail)
1499 if (array_intersect($messages[$id][
'__from'], [$blacklistMail->convertDomainToPunycode()]))
1501 $targetMessages[$id] = $item[
'UID'];
1508 if (!empty($blacklist[
'domain']))
1510 foreach ($messages[$id][
'__from'] as $email)
1512 $domain = mb_substr($email, mb_strrpos($email,
'@'));
1513 if (in_array($domain, $domains))
1515 $targetMessages[$id] = $item[
'UID'];
1523 if (!empty($targetMessages))
1525 if ($this->client->moveMails($targetMessages, $dirPath, $targetDir)->isSuccess())
1527 $messages = array_diff_key($messages, $targetMessages);
1534 return md5(sprintf(
'%s:%u:%u', $dirPath, $uidToken, $UID));
1541 trim($message[
'BODY[HEADER]']),
1542 $message[
'INTERNALDATE'],
1543 $message[
'RFC822.SIZE']
1550 $existingMessagesId = [];
1553 reset($messages)[
'UID'],
1554 end($messages)[
'UID'],
1563 '=DIR_MD5' => md5(Emoji::encode($dirPath)),
1564 '=DIR_UIDV' => $uidToken,
1565 '>=MSG_UID' => $range[0],
1566 '<=MSG_UID' => $range[1],
1570 while ($item = $result->fetch())
1572 $existingMessagesId[] = $item[
'ID'];
1575 foreach ($messages as $id => $item)
1579 if (in_array($messageUid, $existingMessagesId))
1581 unset($messages[$id]);
1586 $existingMessagesId[] = $messageUid;
1593 'select' => [
'HEADER_MD5',
'MESSAGE_ID',
'DATE_INSERT'],
1595 '@HEADER_MD5' => $headerHashes,
1603 'select' => array(
'ID',
'MESSAGE_ID',
'DATE_INSERT'),
1605 '@ID' => array_values($idsForDataBase),
1613 $idsForDataBase = [];
1615 foreach ($messages as $id => $item)
1617 $hashes[$id] = $item[
'__fields'][
'HEADER_MD5'];
1618 $idsForDataBase[$id] = $item[
'__fields'][
'ID'];
1623 foreach ($hashes as $id => $hash)
1625 if (!array_key_exists($hash, $hashesMap))
1627 $hashesMap[$hash] = [];
1630 $hashesMap[$hash][] = $id;
1640 while ($item = $existingMessages->fetch())
1642 foreach ((array)$hashesMap[$item[
'HEADER_MD5']] as $id)
1644 $messages[$id][
'__created'] = $item[
'DATE_INSERT'];
1645 $messages[$id][
'__fields'][
'MESSAGE_ID'] = $item[
'MESSAGE_ID'];
1655 while ($item = $existingMessages->fetch())
1657 $id = array_search($item[
'ID'], $idsForDataBase);
1658 $messages[$id][
'__created'] = $item[
'DATE_INSERT'];
1659 $messages[$id][
'__fields'][
'MESSAGE_ID'] = $item[
'MESSAGE_ID'];
1660 $messages[$id][
'__replaces'] = $item[
'ID'];
1666 $message[
'__internaldate'] = Main\Type\DateTime::createFromPhp(
1667 \DateTime::createFromFormat(
1669 ltrim(trim($message[
'INTERNALDATE']),
'0')
1673 $message[
'__fields'] = [
1675 'DIR_MD5' => md5(Emoji::encode($dirPath)),
1676 'DIR_UIDV' => $uidToken,
1677 'MSG_UID' => $message[
'UID'],
1678 'INTERNALDATE' => $message[
'__internaldate'],
1679 'IS_SEEN' => (isset($message[
'FLAGS']) && preg_grep(
'/^ \x5c Seen $/ix', $message[
'FLAGS'])) ?
'Y' :
'N',
1687 if (preg_match(
'/X-Bitrix-Mail-Message-UID:\s*([a-f0-9]+)/i', $message[
'BODY[HEADER]'], $matches))
1702 reset($messages)[
'UID'],
1703 end($messages)[
'UID'],
1708 'select' => array(
'ID',
'MESSAGE_ID',
'IS_SEEN'),
1710 '=DIR_MD5' => md5($dirPath),
1711 '=DIR_UIDV' => $uidtoken,
1712 '>=MSG_UID' => $range[0],
1713 '<=MSG_UID' => $range[1],
1717 while ($item = $result->fetch())
1719 $item[
'MAILBOX_USER_ID'] = $this->mailbox[
'USER_ID'];
1720 $excerpt[$item[
'ID']] = $item;
1729 foreach ($messages as $id => $item)
1731 $messageUid = md5(sprintf(
'%s:%u:%u', $dirPath, $uidtoken, $item[
'UID']));
1733 if (array_key_exists($messageUid, $excerpt))
1735 $excerptSeen = $excerpt[$messageUid][
'IS_SEEN'];
1736 $excerptSeenYN = in_array($excerptSeen, array(
'Y',
'S')) ?
'Y' :
'N';
1737 $messageSeen = preg_grep(
'/^ \x5c Seen $/ix', $item[
'FLAGS']) ?
'Y' :
'N';
1739 if ($messageSeen != $excerptSeen)
1741 if (in_array($excerptSeen, array(
'S',
'U')))
1743 $excerpt[$messageUid][
'IS_SEEN'] = $excerptSeenYN;
1744 $update[$excerptSeenYN][$messageUid] = $excerpt[$messageUid];
1746 if ($messageSeen != $excerptSeenYN)
1748 $update[$excerptSeen][] = $item[
'UID'];
1753 $excerpt[$messageUid][
'IS_SEEN'] = $messageSeen;
1754 $update[$messageSeen][$messageUid] = $excerpt[$messageUid];
1758 unset($excerpt[$messageUid]);
1775 $countDeleted = count($excerpt);
1777 foreach ($update as $seen => $items)
1781 if (in_array($seen, array(
'S',
'U')))
1783 $method =
'S' == $seen ?
'seen' :
'unseen';
1784 $this->client->$method($items, $dirPath);
1788 $countUpdated += count($items);
1792 '@ID' => array_keys($items),
1803 if (!empty($excerpt))
1807 '@ID' => array_keys($excerpt),
1808 '=DIR_MD5' => md5($dirPath),
1811 'info' =>
'deletion of non-existent messages',
1815 $countDeleted += $result ? $result->getCount() : 0;
1818 $this->lastSyncResult[
'updatedMessages'] += $countUpdated;
1819 $this->lastSyncResult[
'deletedMessages'] += $countDeleted;
1824 $result = Mail\MailMessageUidTable::update(
1827 'MAILBOX_ID' => $this->mailbox[
'ID'],
1834 return $result->isSuccess();
1837 protected function syncMessage($dirPath, $message, &$hashesMap = [], $ignoreSyncFrom =
false, $isOutgoing =
false)
1839 $fields = $message[
'__fields'];
1841 if ($fields[
'MESSAGE_ID'] > 0)
1843 $hashesMap[$fields[
'HEADER_MD5']] = $fields[
'MESSAGE_ID'];
1847 if (array_key_exists($fields[
'HEADER_MD5'], $hashesMap) && $hashesMap[$fields[
'HEADER_MD5']] > 0)
1849 $fields[
'MESSAGE_ID'] = $hashesMap[$fields[
'HEADER_MD5']];
1853 if (!$this->
registerMessage($fields, ($message[
'__replaces'] ??
null), $isOutgoing))
1860 if($minimumSyncDate !==
false && !$ignoreSyncFrom && $message[
'__internaldate']->getTimestamp() < $this->
getMinimumSyncDate())
1866 if (!empty($message[
'__created']) && !empty($this->mailbox[
'OPTIONS'][
'resync_from']))
1868 if ($message[
'__created']->getTimestamp() < $this->mailbox[
'OPTIONS'][
'resync_from'])
1875 if ($fields[
'MESSAGE_ID'] > 0)
1883 if (!empty($message[
'BODYSTRUCTURE']) && !empty($message[
'BODY[HEADER]']))
1888 $message[
'__fields'],
1889 $message[
'__bodystructure'],
1894 if (!$message[
'__bodystructure']->isMultipart())
1896 if (is_array($message[
'__parts']) && !empty($message[
'__parts'][
'BODY[1]']))
1898 $message[
'__parts'][
'BODY[1.MIME]'] = $message[
'BODY[HEADER]'];
1905 $message[
'__parts'] = $this->
downloadMessage($message[
'__fields']) ?:
false;
1908 if (
false !== $message[
'__parts'])
1915 'timestamp' => $message[
'__internaldate']->getTimestamp(),
1916 'size' => $message[
'RFC822.SIZE'],
1917 'outcome' => in_array($this->mailbox[
'EMAIL'], $message[
'__from']),
1918 'draft' => $dir !=
null && $dir->isDraft() || (isset($message[
'FLAGS']) && preg_grep(
'/^ \x5c Draft $/ix', $message[
'FLAGS'])),
1919 'trash' => $dir !=
null && $dir->isTrash(),
1920 'spam' => $dir !=
null && $dir->isSpam(),
1921 'seen' => $fields[
'IS_SEEN'] ==
'Y',
1922 'hash' => $fields[
'HEADER_MD5'],
1923 'lazy_attachments' => $this->isSupportLazyAttachments(),
1924 'excerpt' => $fields,
1932 $hashesMap[$fields[
'HEADER_MD5']] = $messageId;
1939 return $messageId > 0;
1944 if (empty($excerpt[
'MSG_UID']) || empty($excerpt[
'DIR_MD5']))
1949 $dirPath = $this->
getDirsHelper()->getDirPathByHash($excerpt[
'DIR_MD5']);
1950 if (empty($dirPath))
1955 $message = $this->client->fetch(
true, $dirPath, $excerpt[
'MSG_UID'],
'(BODYSTRUCTURE)', $error);
1956 if (empty($message[
'BODYSTRUCTURE']))
1960 if (
false === $message)
1968 if (!is_array($message[
'BODYSTRUCTURE']))
1971 new Main\
Error(
'Helper\Mailbox\Imap: Invalid BODYSTRUCTURE', 0),
1972 new Main\
Error((
string)$message[
'BODYSTRUCTURE'], -1),
1981 $message[
'__bodystructure'],
1982 self::MESSAGE_PARTS_ATTACHMENT
1985 $attachments = array();
1987 $message[
'__bodystructure']->traverse(
1988 function (
Mail\
Imap\BodyStructure $item) use (&$parts, &$attachments)
1990 if ($item->isMultipart() || $item->isBodyText())
1995 $attachments[] = \CMailMessage::decodeMessageBody(
1996 \CMailMessage::parseHeader(
1997 $parts[sprintf(
'BODY[%s.MIME]', $item->getNumber())],
1998 $this->mailbox[
'LANG_CHARSET']
2000 $parts[sprintf(
'BODY[%s]', $item->getNumber())],
2001 $this->mailbox[
'LANG_CHARSET']
2006 return $attachments;
2011 if (!is_array($message))
2013 return parent::cacheMessage($message, $params);
2016 if (!is_array($message[
'__parts']))
2018 return parent::cacheMessage($message[
'__parts'], $params);
2021 if (empty($message[
'__header']))
2026 if (empty($message[
'__bodystructure']) || !($message[
'__bodystructure'] instanceof
Mail\
Imap\BodyStructure))
2031 $params[
'log_parts'] = $message[
'__parts'];
2033 $complete =
function (&$html, &$text)
2035 if (
'' !== $html &&
'' === $text)
2037 $text = html_entity_decode(
2039 ENT_QUOTES | ENT_HTML401,
2040 $this->mailbox[
'LANG_CHARSET']
2043 elseif (
'' === $html &&
'' !== $text)
2045 $html = txtToHtml($text,
false, 120);
2049 [$bodyHtml, $bodyText, $attachments] = $message[
'__bodystructure']->traverse(
2050 function (
Mail\
Imap\BodyStructure $item, &$subparts) use (&$message, &$complete)
2052 $parts = &$message[
'__parts'];
2056 $attachments = array();
2058 if ($item->isMultipart())
2060 if (
'alternative' === $item->getSubtype())
2062 foreach ($subparts as $part)
2066 if (
'' !== $part[0])
2071 if (
'' !== $part[1])
2076 if (!empty($part[2]))
2078 $attachments = array_merge($attachments, $part[2]);
2082 $complete($html, $text);
2086 foreach ($subparts as $part)
2090 $complete($part[0], $part[1]);
2092 if (
'' !== $part[0] ||
'' !== $part[1])
2094 $html .= $part[0] .
"\r\n\r\n";
2095 $text .= $part[1] .
"\r\n\r\n";
2098 $attachments = array_merge($attachments, $part[2]);
2102 $html = trim($html);
2103 $text = trim($text);
2107 if (array_key_exists(sprintf(
'BODY[%s]', $item->getNumber()), $parts))
2109 $part = \CMailMessage::decodeMessageBody(
2110 \CMailMessage::parseHeader(
2111 $parts[sprintf(
'BODY[%s.MIME]', $item->getNumber())],
2112 $this->mailbox[
'LANG_CHARSET']
2114 $parts[sprintf(
'BODY[%s]', $item->getNumber())],
2115 $this->mailbox[
'LANG_CHARSET']
2121 'CONTENT-TYPE' => $item->getType() .
'/' . $item->getSubtype(),
2122 'CONTENT-ID' => $item->getId(),
2124 'FILENAME' => $item->getParams()[
'name']
2128 if (!$item->isBodyText())
2130 $attachments[] = $part;
2132 elseif (!empty($part))
2134 if (
'html' === $item->getSubtype())
2136 $html = $part[
'BODY'];
2140 $text = $part[
'BODY'];
2145 return array($html, $text, $attachments);
2149 $complete($bodyHtml, $bodyText);
2151 return \CMailMessage::saveMessage(
2152 $this->mailbox[
'ID'],
2154 $message[
'__header'],
2164 $minimumDate =
false;
2166 if(!empty($this->mailbox[
'OPTIONS'][
'sync_from']))
2168 $minimumDate = $this->mailbox[
'OPTIONS'][
'sync_from'];
2171 $syncOldLimit = Mail\Helper\LicenseManager::getSyncOldLimit();
2173 if($syncOldLimit > 0)
2175 $syncOldLimit = strtotime(sprintf(
'-%u days', $syncOldLimit));
2180 if($minimumDate ===
false || $minimumDate < $syncOldLimit)
2182 $minimumDate = $syncOldLimit;
2185 return $minimumDate;
2188 protected function getSyncRange($dirPath, &$uidtoken, $intervalSynchronizationAttempts = 0)
2190 $meta = $this->client->select($dirPath, $error);
2191 if (
false === $meta)
2193 $this->warnings->add($this->client->getErrors()->toArray());
2198 if (!($meta[
'exists'] > 0))
2203 $uidtoken = $meta[
'uidvalidity'];
2211 $rangeGetter =
function ($min, $max) use ($dirPath, $uidtoken, &$rangeGetter, $maximumLengthSynchronizationInterval)
2214 $size = $max - $min + 1;
2217 $d = $size < 1000 ? $maximumLengthSynchronizationInterval : pow(10, round(ceil(log10($size) - 0.7) / 2) * 2 - 2);
2220 for ($i = $min; $i <= $max; $i = $i + $d)
2228 if (count($set) > 1 && end($set) >= $max)
2237 $set = $this->client->fetch(
false, $dirPath, join(
',', $set),
'(UID)', $error);
2246 static $uidMinInDatabase, $uidMaxInDatabase, $takeFromDown;
2248 if (!isset($uidMinInDatabase, $uidMaxInDatabase, $takeFromDown))
2250 $messagesUidBoundariesIntervalInDatabase = $this->
getUidRange($dirPath, $uidtoken);
2252 if ($messagesUidBoundariesIntervalInDatabase)
2254 $uidMinInDatabase = $messagesUidBoundariesIntervalInDatabase[
'MIN'];
2255 $uidMaxInDatabase = $messagesUidBoundariesIntervalInDatabase[
'MAX'];
2256 $takeFromDown = $messagesUidBoundariesIntervalInDatabase[
'TAKE_FROM_DOWN'];
2260 $takeFromDown =
true;
2261 $uidMinInDatabase = $uidMaxInDatabase = (end($set)[
'UID'] + 1);
2265 if (count($set) == 1)
2267 $uid = reset($set)[
'UID'];
2269 if ($uid > $uidMaxInDatabase || $uid < $uidMinInDatabase)
2271 return array($uid, $uid);
2274 elseif (end($set)[
'UID'] > $uidMaxInDatabase)
2283 $max = current($set)[
'id'];
2284 $min = prev($set)[
'id'];
2286 while (current($set)[
'UID'] > $uidMaxInDatabase && prev($set) && next($set));
2288 if ($max - $min > $maximumLengthSynchronizationInterval)
2290 return $rangeGetter($min, $max);
2301 max($set[$min][
'UID'], $uidMaxInDatabase + 1),
2306 elseif (reset($set)[
'UID'] < $uidMinInDatabase && $takeFromDown)
2310 $min = current($set)[
'id'];
2311 $max = next($set)[
'id'];
2313 while (current($set)[
'UID'] < $uidMinInDatabase && next($set) && prev($set));
2315 if ($max - $min > $maximumLengthSynchronizationInterval)
2317 return $rangeGetter($min, $max);
2328 min($set[$max][
'UID'], $uidMinInDatabase - 1),
2337 return $rangeGetter(1, $meta[
'exists']);
2343 '=DIR_MD5' => md5(Emoji::encode($dirPath)),
2344 '=DIR_UIDV' => $uidtoken,
2350 $takeFromDown =
true;
2355 'MIN' =>
'MSG_UID',
'INTERNALDATE'
2357 'filter' => $filter,
2366 if(isset($min[
'INTERNALDATE']) && $minimumSyncDate !==
false && $min[
'INTERNALDATE']->getTimestamp() < $minimumSyncDate)
2368 $takeFromDown =
false;
2376 'filter' => $filter,
2378 'MSG_UID' =>
'DESC',
2388 'MIN' => $min[
'MIN'],
2389 'MAX' => $max[
'MAX'],
2390 'TAKE_FROM_DOWN' => $takeFromDown,
syncDirForSpecificDay($dirPath, $internalDate)
resyncDir($dirPath, $numberForResync=false)
downloadAttachments(array &$excerpt)
selectOutgoingMessageIdFromHeader($message)
createMessage(Main\Mail\Mail $message, array $fields=array())
getUidRange($dirPath, $uidtoken)
listDirs($pattern, $useDb=false)
downloadMessageParts(array &$excerpt, Mail\Imap\BodyStructure $bodystructure, $flags=Imap::MESSAGE_PARTS_ALL)
getSyncRange($dirPath, &$uidtoken, $intervalSynchronizationAttempts=0)
const MESSAGE_PARTS_ATTACHMENT
const MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS
setIsOldStatusesLowerThan($internalDate, $dirPath, $mailboxId)
resyncDirInternal($dir, $numberForResync=false)
buildMessageIdForDataBase($dirPath, $uidToken, $UID)
downloadMessage(array &$excerpt)
syncMessage($dirPath, $message, &$hashesMap=[], $ignoreSyncFrom=false, $isOutgoing=false)
getMaximumSynchronizationLengthsOfIntervals($num)
resyncMessages($dirPath, $uidtoken, &$messages)
checkMessagesForExistence($dirPath='INBOX', $UIDs=[])
completeMessageSync($uid)
searchExistingMessagesByHeaderInDataBase($headerHashes)
uploadMessage(Main\Mail\Mail $message, array &$excerpt=null)
cacheMessage(&$message, $params=array())
removeExistingMessagesFromSynchronizationList($dirPath, $uidToken, &$messages)
moveMailsToFolder($messages, $folderTo)
syncMessages($mailboxID, $dirPath, $UIDs)
linkWithExistingMessages(&$messages)
buildMessageHeaderHashForDataBase($message)
getFolderToMessagesMap($messages)
searchExistingMessagesByIdInDataBase($idsForDataBase)
fillMessageFields(&$message, $dirPath, $uidToken)
static getCurrentSyncDir()
static setCurrentSyncDir(string $path)
registerMessage(&$fields, $replaces=null, $isOutgoing=false)
updateMessagesRegistry(array $filter, array $fields, $mailData=array())
listMessages($params=array(), $fetch=true)
setLastSyncResult(array $data)
isSupportLazyAttachments()
unregisterMessages($filter, $eventData=[], $ignoreDeletionCheck=false)
const FIELD_SANITIZE_ON_VIEW
static updateSyncTime($id, $val)