1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
imap.php
См. документацию.
1<?php
2
4
12
14{
19 100,
20 50,
21 25,
22 12,
23 6,
24 3,
25 1
26 ];
27
29 {
30 if(isset(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[$num]))
31 {
32 return self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[$num];
33 }
34 else
35 {
36 return self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS[count(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS)-1];
37 }
38 }
39
40 protected $client;
41
42 protected function __construct($mailbox)
43 {
44 parent::__construct($mailbox);
45
46 $this->client = new Mail\Imap(
47 $mailbox['SERVER'],
48 $mailbox['PORT'],
49 $mailbox['USE_TLS'] == 'Y' || $mailbox['USE_TLS'] == 'S',
50 $mailbox['USE_TLS'] == 'Y',
51 $mailbox['LOGIN'],
52 $mailbox['PASSWORD']
53 );
54 }
55
56 public function getSyncStatusTotal()
57 {
58 $currentDir = null;
59
60 if (!empty($this->syncParams['currentDir']))
61 {
62 $currentDir = $this->syncParams['currentDir'];
63 }
64
65 $totalSyncDirs = count($this->getDirsHelper()->getSyncDirs());
66 $currentSyncDirPath = MailboxDirectoryHelper::getCurrentSyncDir();
67 $currentSyncDir = $this->getDirsHelper()->getDirByPath($currentSyncDirPath);
68
69 if ($totalSyncDirs > 0 && $currentSyncDir != null)
70 {
71 $currentSyncDirMessages = Mail\MailMessageUidTable::getList([
72 'select' => [
73 new Main\Entity\ExpressionField('TOTAL', 'COUNT(1)'),
74 ],
75 'filter' => [
76 '=MAILBOX_ID' => $this->mailbox['ID'],
77 '=DIR_MD5' => $currentSyncDir->getDirMd5(),
78 '==DELETE_TIME' => 0,
79 ],
80 ])->fetch();
81
82 $currentSyncDirMessagesCount = (int)$currentSyncDirMessages['TOTAL'];
83 $currentSyncDirMessagesAll = (int)$currentSyncDir->getMessageCount();
84 $currentSyncDirPosition = $this->getDirsHelper()->getCurrentSyncDirPositionByDefault(
85 $currentSyncDir->getPath(),
86 $currentDir
87 );
88
89 if ($currentDir != null) {
90 $totalSyncDirs--;
91 }
92
93 if ($currentSyncDirMessagesAll <= 0)
94 {
95 $progress = ($currentSyncDirPosition + 1) / $totalSyncDirs;
96 }
97 else
98 {
99 $progress = ($currentSyncDirMessagesCount / $currentSyncDirMessagesAll + $currentSyncDirPosition) / $totalSyncDirs;
100 }
101
102 return $progress;
103 }
104 else
105 {
106 return parent::getSyncStatus();
107 }
108 }
109
110 public function getSyncStatus()
111 {
112 if (!empty($this->syncParams['currentDir']))
113 {
114 $currentSyncDir = $this->getDirsHelper()->getDirByPath($this->syncParams['currentDir']);
115 }
116
117 if (!empty($currentSyncDir))
118 {
119 $currentSyncDirMessages = Mail\MailMessageUidTable::getList([
120 'select' => [
121 new Main\Entity\ExpressionField('TOTAL', 'COUNT(1)'),
122 ],
123 'filter' => [
124 '=MAILBOX_ID' => $this->mailbox['ID'],
125 '=DIR_MD5' => $currentSyncDir->getDirMd5(),
126 '==DELETE_TIME' => 0,
127 ],
128 ])->fetch();
129
130 $currentSyncDirMessagesCount = (int) $currentSyncDirMessages['TOTAL'];
131 $currentSyncDirMessagesAll = (int) $currentSyncDir->getMessageCount();
132
133 if ($currentSyncDirMessagesAll > 0)
134 {
135 return ($currentSyncDirMessagesCount / $currentSyncDirMessagesAll);
136 }
137 }
138
139 return 1;
140 }
141
142 public function checkMessagesForExistence($dirPath ='INBOX',$UIDs = [])
143 {
144 if(!empty($UIDs))
145 {
146 /*
147 If a non-existing id gets among the existing ones,
148 some mailers may issue an error (instead of issuing existing messages),
149 then we will think that the letters disappeared on the mail service,
150 although in fact there were existing messages among them.
151 But the messages can be deleted legally,
152 it's just that the mail has not been resynchronized for a long time.
153 In this case, small samples are needed in order to catch existing messages in any of them.
154 */
155 $chunks = array_chunk($UIDs, 5);
156
157 $existingMessage = NULL;
158
159 foreach ($chunks as $chunk)
160 {
161 $messages = $this->client->fetch(
162 true,
163 $dirPath,
164 join(',', $chunk),
165 '(UID FLAGS)',
166 $error,
167 'list'
168 );
169
170 if(!($messages === false || empty($messages)))
171 {
172 foreach ($messages as $item)
173 {
174 if(!isset($item['FLAGS']))
175 {
176 continue;
177 }
178
179 $messageDeleted = preg_grep('/^ \x5c Deleted $/ix', $item['FLAGS']) ? true : false;
180
181 if(!$messageDeleted)
182 {
183 $existingMessage = $item;
184 break;
185 }
186 }
187 }
188 }
189
190 if(!is_null($existingMessage))
191 {
192 if(isset($existingMessage['UID']))
193 {
194 return $existingMessage['UID'];
195 }
196 }
197 }
198
199 return false;
200 }
201
202 public function resyncIsOldStatus()
203 {
204 $mailboxID = $this->mailbox['ID'];
205 $directoryHelper = new Mail\Helper\MailboxDirectoryHelper($mailboxID);
206 $syncDirs = $directoryHelper->getSyncDirs();
207
208 $numberOfUnSynchronizedDirs = count($syncDirs);
209
210 foreach ($syncDirs as $dir)
211 {
212 $dirPath = $dir->getPath();
213 $dirId = $dir->getId();
214
215 $internalDate = \Bitrix\Mail\Helper::getLastDeletedOldMessageInternaldate($mailboxID, $dirPath);
216
217 $keyRow = [
218 'MAILBOX_ID' => $mailboxID,
219 'ENTITY_TYPE' => 'DIR',
220 'ENTITY_ID' => $dirId,
221 'PROPERTY_NAME' => 'SYNC_IS_OLD_STATUS',
222 ];
223
224 $filter = [
225 '=MAILBOX_ID' => $keyRow['MAILBOX_ID'],
226 '=ENTITY_TYPE' => $keyRow['ENTITY_TYPE'],
227 '=ENTITY_ID' => $keyRow['ENTITY_ID'],
228 '=PROPERTY_NAME' => $keyRow['PROPERTY_NAME'],
229 ];
230
231 $startValue = 'started_for_date_'.$internalDate;
232
233 if(Mail\Internals\MailEntityOptionsTable::getCount($filter))
234 {
235 if(Mail\Internals\MailEntityOptionsTable::getList([
236 'select' => [
237 'VALUE',
238 ],
239 'filter' => $filter,
240 ])->fetchAll()[0]['VALUE'] !== 'completed')
241 {
243 $keyRow,
244 ['VALUE' => $startValue]
245 );
246
247 $synchronizationSuccess = $this->setIsOldStatusesLowerThan($internalDate,$dirPath,$mailboxID);
248
249 if($synchronizationSuccess)
250 {
252 $keyRow,
253 ['VALUE' => 'completed']
254 );
255 $numberOfUnSynchronizedDirs--;
256 }
257 }
258 else
259 {
260 $numberOfUnSynchronizedDirs--;
261 }
262 }
263 else
264 {
265 $fields = $keyRow;
266 $fields['VALUE'] = $startValue;
268 $fields
269 );
270
271 $synchronizationSuccess = $this->setIsOldStatusesLowerThan($internalDate,$dirPath,$mailboxID);
272
273 if($synchronizationSuccess)
274 {
276 $keyRow,
277 ['VALUE' => 'completed']
278 );
279 $numberOfUnSynchronizedDirs--;
280 }
281 }
282 }
283
284 if($numberOfUnSynchronizedDirs === 0)
285 {
286 return true;
287 }
288 else
289 {
290 return false;
291 }
292 }
293
294 public function syncFirstDay()
295 {
296 $mailboxID = $this->mailbox['ID'];
297 $directoryHelper = new Mail\Helper\MailboxDirectoryHelper($mailboxID);
298 $syncDirs = $directoryHelper->getSyncDirs();
299
300 $numberOfUnSynchronizedDirs = count($syncDirs);
301
302 foreach ($syncDirs as $dir)
303 {
304 $dirPath = $dir->getPath();
305 $dirId = $dir->getId();
306
307 $internalDate = \Bitrix\Mail\Helper::getStartInternalDateForDir($mailboxID,$dirPath);
308
309 $keyRow = [
310 'MAILBOX_ID' => $mailboxID,
311 'ENTITY_TYPE' => 'DIR',
312 'ENTITY_ID' => $dirId,
313 'PROPERTY_NAME' => 'SYNC_FIRST_DAY',
314 ];
315
316 $filter = [
317 '=MAILBOX_ID' => $keyRow['MAILBOX_ID'],
318 '=ENTITY_TYPE' => $keyRow['ENTITY_TYPE'],
319 '=ENTITY_ID' => $keyRow['ENTITY_ID'],
320 '=PROPERTY_NAME' => $keyRow['PROPERTY_NAME'],
321 ];
322
323 $startValue = 'started_for_date_'.$internalDate;
324
325 if(Mail\Internals\MailEntityOptionsTable::getCount($filter))
326 {
327 if(Mail\Internals\MailEntityOptionsTable::getList([
328 'select' => [
329 'VALUE',
330 ],
331 'filter' => $filter,
332 ])->fetchAll()[0]['VALUE'] !== 'completed')
333 {
335 $keyRow,
336 ['VALUE' => $startValue]
337 );
338
339 \CTimeZone::Disable();
340 $synchronizationSuccess = $this->syncDirForSpecificDay($dirPath,$internalDate);
341 \CTimeZone::Enable();
342
343 if($synchronizationSuccess)
344 {
346 $keyRow,
347 ['VALUE' => 'completed']
348 );
349 $numberOfUnSynchronizedDirs--;
350 }
351 }
352 else
353 {
354 $numberOfUnSynchronizedDirs--;
355 }
356 }
357 else
358 {
359 $fields = $keyRow;
360 $fields['VALUE'] = $startValue;
362 $fields
363 );
364
365 \CTimeZone::Disable();
366 $synchronizationSuccess = $this->syncDirForSpecificDay($dirPath,$internalDate);
367 \CTimeZone::Enable();
368
369 if($synchronizationSuccess)
370 {
372 $keyRow,
373 ['VALUE' => 'completed']
374 );
375 $numberOfUnSynchronizedDirs--;
376 }
377 }
378 }
379
380 if($numberOfUnSynchronizedDirs === 0)
381 {
382 return true;
383 }
384 else
385 {
386 return false;
387 }
388 }
389
390 protected function syncInternal()
391 {
392 $syncReport = $this->syncMailbox();
393 if (false === $syncReport['syncCount'])
394 {
395 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
396 }
397
398 return $syncReport;
399 }
400
402 {
403 $dirPath = $this->getDirsHelper()->getOutcomePath() ?: 'INBOX';
404
405 $fields = array_merge(
406 $fields,
407 array(
408 'DIR_MD5' => md5($dirPath),
409 'DIR_UIDV' => 0,
410 'MSG_UID' => 0,
411 )
412 );
413
414 return parent::createMessage($message, $fields);
415 }
416
417 public function syncOutgoing()
418 {
419 $this->cacheDirs();
420
421 parent::syncOutgoing();
422 }
423
424 public function uploadMessage(Main\Mail\Mail $message, array &$excerpt = null)
425 {
426 $dirPath = $this->getDirsHelper()->getOutcomePath() ?: 'INBOX';
427
428 $data = $this->client->select($dirPath, $error);
429
430 if (false === $data)
431 {
432 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
433
434 return false;
435 }
436
437 if (!empty($excerpt['__unique_headers']))
438 {
439 if ($this->client->searchByHeader(false, $dirPath, $excerpt['__unique_headers'], $error))
440 {
441 return false;
442 }
443 }
444
445 if (!empty($excerpt['ID']))
446 {
447 class_exists('Bitrix\Mail\Helper');
448
450 $message,
451 array(
452 'X-Bitrix-Mail-Message-UID' => $excerpt['ID'],
453 )
454 );
455 }
456
457 $result = $this->client->append(
458 $dirPath,
459 array('\Seen'),
460 new \DateTime,
461 sprintf(
462 '%1$s%3$s%3$s%2$s',
463 $message->getHeaders(),
464 $message->getBody(),
465 $message->getMailEol()
466 ),
467 $error
468 );
469
470 if (false === $result)
471 {
472 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
473
474 return false;
475 }
476
477 $this->syncDir($dirPath);
478
479 return $result;
480 }
481
482 public function downloadMessage(array &$excerpt)
483 {
484 if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5']))
485 {
486 return false;
487 }
488
489 $dirPath = $this->getDirsHelper()->getDirPathByHash($excerpt['DIR_MD5']);
490 if (empty($dirPath))
491 {
492 return false;
493 }
494
495 $body = $this->client->fetch(true, $dirPath, $excerpt['MSG_UID'], '(BODY.PEEK[])', $error);
496
497 if (false === $body)
498 {
499 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
500
501 return false;
502 }
503
504 return empty($body['BODY[]']) ? null : $body['BODY[]'];
505 }
506
507 public function downloadMessageParts(array &$excerpt, Mail\Imap\BodyStructure $bodystructure, $flags = Imap::MESSAGE_PARTS_ALL)
508 {
509 if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5']))
510 {
511 return false;
512 }
513
514 $dirPath = $this->getDirsHelper()->getDirPathByHash($excerpt['DIR_MD5']);
515 if (empty($dirPath))
516 {
517 return false;
518 }
519
520 $rfc822Parts = array();
521
522 $select = array_filter(
523 $bodystructure->traverse(
524 function (Mail\Imap\BodyStructure $item) use ($flags, &$rfc822Parts)
525 {
526 if ($item->isMultipart())
527 {
528 return;
529 }
530
531 $isTextItem = $item->isBodyText();
532 if ($flags & ($isTextItem ? Imap::MESSAGE_PARTS_TEXT : Imap::MESSAGE_PARTS_ATTACHMENT))
533 {
534 // due to yandex bug
535 if ('message' === $item->getType() && 'rfc822' === $item->getSubtype())
536 {
537 $rfc822Parts[] = $item;
538
539 return sprintf('BODY.PEEK[%1$s.HEADER] BODY.PEEK[%1$s.TEXT]', $item->getNumber());
540 }
541
542 return sprintf('BODY.PEEK[%1$s.MIME] BODY.PEEK[%1$s]', $item->getNumber());
543 }
544 },
545 true
546 )
547 );
548
549 if (empty($select))
550 {
551 return array();
552 }
553
554 $parts = $this->client->fetch(
555 true,
556 $dirPath,
557 $excerpt['MSG_UID'],
558 sprintf('(%s)', join(' ', $select)),
559 $error
560 );
561
562 if (false === $parts)
563 {
564 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
565
566 return false;
567 }
568
569 foreach ($rfc822Parts as $item)
570 {
571 $headerKey = sprintf('BODY[%s.HEADER]', $item->getNumber());
572 $bodyKey = sprintf('BODY[%s.TEXT]', $item->getNumber());
573
574 if (array_key_exists($headerKey, $parts) || array_key_exists($bodyKey, $parts))
575 {
576 $partMime = 'Content-Type: message/rfc822';
577 if (!empty($item->getParams()['name']))
578 {
579 $partMime .= sprintf('; name="%s"', $item->getParams()['name']);
580 }
581
582 if (!empty($item->getDisposition()[0]))
583 {
584 $partMime .= sprintf("\r\nContent-Disposition: %s", $item->getDisposition()[0]);
585 if (!empty($item->getDisposition()[1]) && is_array($item->getDisposition()[1]))
586 {
587 foreach ($item->getDisposition()[1] as $name => $value)
588 {
589 $partMime .= sprintf('; %s="%s"', $name, $value);
590 }
591 }
592 }
593
594 $parts[sprintf('BODY[%1$s.MIME]', $item->getNumber())] = $partMime;
595 $parts[sprintf('BODY[%1$s]', $item->getNumber())] = sprintf(
596 "%s\r\n\r\n%s",
597 rtrim($parts[$headerKey], "\r\n"),
598 ltrim($parts[$bodyKey], "\r\n")
599 );
600
601 unset($parts[$headerKey], $parts[$bodyKey]);
602 }
603 }
604
605 return $parts;
606 }
607
608 public function cacheDirs()
609 {
610 static $lastCacheSession;
611
612 if ($this->session === $lastCacheSession)
613 {
614 return;
615 }
616
617 $dirs = $this->client->listex('', '%', $error);
618 if (false === $dirs)
619 {
620 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
621
622 return false;
623 }
624
625 $list = [];
626 foreach ($dirs as $item)
627 {
628 $parts = explode($item['delim'], $item['name']);
629
630 $item['path'] = $item['name'];
631 $item['name'] = end($parts);
632
633 $list[$item['name']] = $item;
634 }
635
636 $this->getDirsHelper()->syncDbDirs($list);
637
638 $lastCacheSession = $this->session;
639 }
640
641 public function listDirs($pattern, $useDb = false)
642 {
643 $dirs = $this->client->listex('', $pattern, $error);
644 if (false === $dirs)
645 {
646 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
647
648 return false;
649 }
650
651 $list = [];
652
653 foreach ($dirs as $dir)
654 {
655 $parts = explode($dir['delim'], $dir['name']);
656
657 $dir['path'] = $dir['name'];
658 $dir['name'] = end($parts);
659 $list[$dir['path']] = $dir;
660 }
661
662 return $list;
663 }
664
665 public function cacheMeta()
666 {
667 return $this->getDirsHelper()->getSyncDirs();
668 }
669
671 {
672 if (isset($messages['MSG_UID']))
673 {
675 }
676 $data = [];
677 $result = new Main\Result();
678 foreach ($messages as $message)
679 {
680 $id = $message['MSG_UID'];
681 $folderFrom = $this->getDirsHelper()->getDirPathByHash($message['DIR_MD5']);
682 $data[$folderFrom][] = $id;
683 $results[$folderFrom][] = $message;
684 }
685 return $result->setData($data);
686 }
687
688 public function markUnseen($messages)
689 {
691 foreach ($result->getData() as $folderFrom => $ids)
692 {
693 $result = $this->client->unseen($ids, $folderFrom);
694 if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
695 {
696 break;
697 }
698 }
699 return $result;
700 }
701
702 public function markSeen($messages)
703 {
705 foreach ($result->getData() as $folderFrom => $ids)
706 {
707 $result = $this->client->seen($ids, $folderFrom);
708 if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
709 {
710 break;
711 }
712 }
713 return $result;
714 }
715
716 public function moveMailsToFolder($messages, $folderTo)
717 {
719 $moveResult = new Main\Result();
720 foreach ($result->getData() as $folderFrom => $ids)
721 {
722 $moveResult = $this->client->moveMails($ids, $folderFrom, $folderTo);
723 if (!$moveResult->isSuccess() || !$this->client->getErrors()->isEmpty())
724 {
725 break;
726 }
727 }
728
729 return $moveResult;
730 }
731
732 public function deleteMails($messages)
733 {
735
736 foreach ($result->getData() as $folderName => $messageId)
737 {
738 $result = $this->client->delete($messageId, $folderName);
739 }
740
741 return $result;
742 }
743
744 public function syncMailbox()
745 {
746 if (!$this->client->authenticate($error))
747 {
748 return false;
749 }
750
751 $syncReport = [
752 'syncCount'=>0,
753 'reSyncCount' => 0,
754 'reSyncStatus' => false,
755 ];
756
757 $this->cacheDirs();
758
759 $currentDir = null;
760
761 if (!empty($this->syncParams['currentDir']))
762 {
763 $currentDir = $this->syncParams['currentDir'];
764 }
765
766 $dirsSync = $this->getDirsHelper()->getSyncDirsOrderByTime($currentDir);
767
768 if (empty($dirsSync))
769 {
770 return $syncReport;
771 }
772
773 $lastDir = $this->getDirsHelper()->getLastSyncDirByDefault($currentDir);
774
776 foreach ($dirsSync as $item)
777 {
779
780 $syncReport['syncCount'] += $this->syncDir($item->getPath());
781
782 if ($this->isTimeQuotaExceeded())
783 {
784 break;
785 }
786
787 MailboxDirectory::updateSyncTime($item->getId(), time());
788
789 if ($lastDir != null && $item->getPath() == $lastDir->getPath())
790 {
792 break;
793 }
794 }
795
796 $this->setLastSyncResult(['updatedMessages' => 0, 'deletedMessages' => 0]);
797
798 if (!$this->isTimeQuotaExceeded())
799 {
800 /* Mark emails from unsynchronized folders (unchecked) for deletion
801
802 It is impossible to check these filters for the legality of deleting messages,
803 since:
804 1) messages do not disappear from the original mailbox
805 2) messages that fall under filters are in different folders,
806 and the check goes through one folder.
807 */
808
810 [
811 '!@DIR_MD5' => array_map(
812 'md5',
813 $this->getDirsHelper()->getSyncDirsPath(true),
814 ),
815 ],
816 [
817 'info' => 'disabled directory synchronization in Bitrix',
818 ],
819 true,
820 );
821
822 $countDeleted = $result ? $result->getCount() : 0;
823
824 $this->lastSyncResult['deletedMessages'] += $countDeleted;
825
826 if (!empty($this->syncParams['full']))
827 {
828 foreach ($dirsSync as $item)
829 {
830 $reSyncReport = $this->resyncDir($item->getPath());
831
832 if($reSyncReport['complete'])
833 {
834 $syncReport['reSyncCount']++;
835 }
836 if ($this->isTimeQuotaExceeded())
837 {
838 break;
839 }
840 }
841
842 if($syncReport['reSyncCount'] === count($dirsSync))
843 {
844 $syncReport['reSyncStatus'] = true;
845 }
846 }
847 }
848
849 return $syncReport;
850 }
851
852 public function syncDir($dirPath)
853 {
854 $dir = $this->getDirsHelper()->getDirByPath($dirPath);
855
856 if (!$dir || !$dir->isSync())
857 {
858 return false;
859 }
860
861 if ($dir->isSyncLock() || !$dir->startSyncLock())
862 {
863 return null;
864 }
865
866 $result = $this->syncDirInternal($dir);
867
868 $dir->stopSyncLock();
869
870 $this->lastSyncResult['newMessages'] += $result;
871 if (!$dir->isTrash() && !$dir->isSpam() && !$dir->isDraft() && !$dir->isOutcome())
872 {
873 $this->lastSyncResult['newMessagesNotify'] += $result;
874 }
875
876 return $result;
877 }
878
879 protected function setIsOldStatusesLowerThan($internalDate, $dirPath, $mailboxId)
880 {
881 if($internalDate === false)
882 {
883 return true;
884 }
885
887 $dir = $dirsHelper->getDirByPath($dirPath);
888
889 $entity = \Bitrix\Mail\MailMessageUidTable::getEntity();
890 $connection = $entity->getConnection();
891
892 $where = sprintf(
893 '(%s)',
894 Main\Entity\Query::buildFilterSql(
895 $entity,
896 [
897 '<=INTERNALDATE' => $internalDate,
898 '=DIR_MD5' => $dir->getDirMd5(),
899 '=MAILBOX_ID' => $mailboxId,
900 '!=IS_OLD' => 'Y',
901 ]
902 )
903 );
904
905 $connection->query(sprintf(
906 'UPDATE %s SET IS_OLD = "Y", IS_SEEN = "Y" WHERE %s LIMIT 1000',
907 $connection->getSqlHelper()->quote($entity->getDbTableName()),
908 $where
909 ));
910
911 if($connection->getAffectedRowsCount() === 0)
912 {
913 return true;
914 }
915 else
916 {
917 return false;
918 }
919 }
920
929 public function syncMessages($mailboxID, $dirPath, $UIDs, $isRecovered = false)
930 {
931 $meta = $this->client->select($dirPath, $error);
932 $uidtoken = $meta['uidvalidity'];
933
934 //checking the dir for existence or authentication failed
935 if (false === $meta)
936 {
937 return true;
938 }
939
941
942 $dir = $dirsHelper->getDirByPath($dirPath);
943
944 $chunks = array_chunk($UIDs, 10);
945
946 $entity = Mail\MailMessageUidTable::getEntity();
947 $connection = $entity->getConnection();
948
949 foreach ($chunks as $chunk)
950 {
951 $connection->query(sprintf(
952 'DELETE FROM %s WHERE %s',
954 Main\Entity\Query::buildFilterSql(
955 $entity,
956 [
957 '@MSG_UID' => $chunk,
958 '=MESSAGE_ID' => 0,
959 '=MAILBOX_ID' => $mailboxID,
960 '=DIR_MD5' => $dir->getDirMd5()
961 ]
962 )
963 ));
964
965 $messages = $this->client->fetch(
966 true,
967 $dirPath,
968 join(',', $chunk),
969 '(UID FLAGS INTERNALDATE RFC822.SIZE BODYSTRUCTURE BODY.PEEK[HEADER])',
970 $error,
971 'list'
972 );
973
974 if (empty($messages))
975 {
976 if ($messages === false)
977 {
978 $this->warnings->add($this->client->getErrors()->toArray());
979 return true;
980 }
981 break;
982 }
983
984 $this->parseHeaders($messages);
985
986 $this->blacklistMessages($dir->getPath(), $messages);
987
988 $this->removeExistingMessagesFromSynchronizationList($dir->getPath(), $uidtoken, $messages);
989
990 foreach ($messages as &$message)
991 {
992 $this->fillMessageFields($message, $dir->getPath(), $uidtoken);
993 }
994
996
997 foreach ($messages as $item)
998 {
999 $isOutgoing = false;
1000
1001 if(empty($item['__replaces']))
1002 {
1003 $outgoingMessageId = $this->getLocalMessageIdFromHeader($item);
1004
1005 if($outgoingMessageId !== '')
1006 {
1007 $item['__replaces'] = $outgoingMessageId;
1008 $isOutgoing = true;
1009 }
1010 }
1011
1012 $hashesMap = [];
1013 $this->syncMessage($dir->getPath(), $item, $hashesMap, true, $isOutgoing, $isRecovered);
1014
1015 if ($this->isTimeQuotaExceeded())
1016 {
1017 return false;
1018 }
1019 }
1020
1021 }
1022 return true;
1023 }
1024
1025 public function isAuthenticated(): bool
1026 {
1027 $dirs = $this->getDirsHelper()->getSyncDirsOrderByTime();
1028
1029 if (empty($dirs))
1030 {
1031 return true;
1032 }
1033
1035 foreach ($dirs as $dir)
1036 {
1037 if (\Bitrix\Mail\Helper::getImapUnseen($this->mailbox, $dir->getPath()) !== false)
1038 {
1039 return true;
1040 }
1041 }
1042
1043 return false;
1044 }
1045
1046 public function syncDirForSpecificDay($dirPath, $internalDate)
1047 {
1048 if($internalDate === false)
1049 {
1050 return true;
1051 }
1052
1053 $mailboxID = $this->mailbox['ID'];
1054
1055 $UIDsOnService = \Bitrix\Mail\Helper::getImapUIDsForSpecificDay($mailboxID, $dirPath, $internalDate);
1056
1057 return $this->syncMessages($mailboxID, $dirPath, $UIDsOnService);
1058 }
1059
1060 protected function syncDirInternal($dir)
1061 {
1062 $messagesSynced = 0;
1063
1064 $error = [];
1065 $meta = $this->client->select($dir->getPath(), $error);
1066
1067 if (false === $meta)
1068 {
1069 $this->warnings->add($this->client->getErrors()->toArray());
1070
1071 if ($this->client->isExistsDir($dir->getPath(), $error) === false)
1072 {
1073 $this->getDirsHelper()->removeDirsLikePath([$dir]);
1074 }
1075
1076 return false;
1077 }
1078
1079 $this->getDirsHelper()->updateMessageCount($dir->getId(), $meta['exists']);
1080
1081 $intervalSynchronizationAttempts = 0;
1082
1083 while ($range = $this->getSyncRange($dir->getPath(), $uidtoken, $intervalSynchronizationAttempts))
1084 {
1085 $syncDown = $range[0] > $range[1];
1086
1087 if ($syncDown)
1088 {
1089 MessageInternalDateHandler::clearStartInternalDate($dir->getMailboxId(), $dir->getDirMd5());
1090 }
1091
1092 sort($range);
1093
1094 $messages = $this->client->fetch(
1095 true,
1096 $dir->getPath(),
1097 join(':', $range),
1098 '(UID FLAGS INTERNALDATE RFC822.SIZE BODYSTRUCTURE BODY.PEEK[HEADER])',
1099 $error
1100 );
1101
1102 $fetchErrors=$this->client->getErrors();
1103 $errorReceivingMessages = $fetchErrors->getErrorByCode(210) !== null;
1104 $failureDueToDataVolume = $fetchErrors->getErrorByCode(104) !== null;
1105
1106 if (empty($messages))
1107 {
1108 if (false === $messages)
1109 {
1110 if($errorReceivingMessages && !$failureDueToDataVolume)
1111 {
1112 /*
1113 Skip the intervals where all the messages were broken
1114 */
1115 return $messagesSynced;
1116 }
1117 elseif($failureDueToDataVolume && $intervalSynchronizationAttempts < count(self::MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS) - 1 )
1118 {
1119 $intervalSynchronizationAttempts++;
1120 continue;
1121 /*
1122 Trying to resynchronize by reducing the interval
1123 */
1124 }
1125 else
1126 {
1127 /*
1128 Fatal errors in which we cannot perform synchronization
1129 */
1130 $this->warnings->add($fetchErrors->toArray());
1131 return false;
1132 }
1133 }
1134 break;
1135 }
1136 else
1137 {
1138 $intervalSynchronizationAttempts = 0;
1139 }
1140
1141 $syncDown ? krsort($messages) : ksort($messages);
1142
1143 $this->parseHeaders($messages);
1144
1145 $this->blacklistMessages($dir->getPath(), $messages);
1146
1147 $this->removeExistingMessagesFromSynchronizationList($dir->getPath(), $uidtoken, $messages);
1148
1149 foreach ($messages as &$message)
1150 {
1151 $this->fillMessageFields($message, $dir->getPath(), $uidtoken);
1152 }
1153
1155
1156 $hashesMap = [];
1157
1158 //To display new messages(grid reload) until synchronization is complete
1159 $numberOfMessagesInABatch = 1;
1160 $numberLeftToFillTheBatch = $numberOfMessagesInABatch;
1161
1162 foreach ($messages as $item)
1163 {
1164 $isOutgoing = false;
1165
1166 if(empty($item['__replaces']))
1167 {
1168 $outgoingMessageId = $this->getLocalMessageIdFromHeader($item);
1169
1170 if($outgoingMessageId !== '')
1171 {
1172 $item['__replaces'] = $outgoingMessageId;
1173 $isOutgoing = true;
1174 }
1175 }
1176
1177 if ($this->syncMessage($dir->getPath(), $item, $hashesMap, false, $isOutgoing))
1178 {
1179 $this->lastSyncResult['newMessageId'] = end($hashesMap);
1180 $messagesSynced++;
1181
1182 $numberLeftToFillTheBatch--;
1183 if($numberLeftToFillTheBatch === 0 and Main\Loader::includeModule('pull'))
1184 {
1185 $numberOfMessagesInABatch *= 2;
1186 $numberLeftToFillTheBatch = $numberOfMessagesInABatch;
1187 \CPullWatch::addToStack(
1188 'mail_mailbox_' . $this->mailbox['ID'],
1189 [
1190 'params' => [
1191 'dir' => $dir->getPath(),
1192 'mailboxId' => $this->mailbox['ID'],
1193 ],
1194 'module_id' => 'mail',
1195 'command' => 'new_message_is_synchronized',
1196 ]
1197 );
1199 }
1200 }
1201
1202 if ($this->isTimeQuotaExceeded())
1203 {
1204 break 2;
1205 }
1206 }
1207 }
1208
1209 if (false === $range)
1210 {
1211 $this->warnings->add($this->client->getErrors()->toArray());
1212
1213 return false;
1214 }
1215
1216 return $messagesSynced;
1217 }
1218
1219 public function resyncDir($dirPath, $numberForResync = false)
1220 {
1221 $dir = $this->getDirsHelper()->getDirByPath($dirPath);
1222
1223 if (!$dir || !$dir->isSync())
1224 {
1225 return false;
1226 }
1227
1228 $report = [
1229 'complete' => false,
1230 'dir' => $dir->getPath(),
1231 'updated' => -$this->lastSyncResult['updatedMessages'],
1232 'deleted' => -$this->lastSyncResult['deletedMessages'],
1233 ];
1234
1235 $result = $this->resyncDirInternal($dir,$numberForResync);
1236
1237 $report['updated'] += $this->lastSyncResult['updatedMessages'];
1238 $report['deleted'] += $this->lastSyncResult['deletedMessages'];
1239
1240 if (false === $result)
1241 {
1242 $report['errors'] = $this->client->getErrors()->toArray();
1243 }
1244 else
1245 {
1246 if($this->isTimeQuotaExceeded())
1247 {
1248 $report['errors'] = [
1249 'isTimeQuotaExceeded'
1250 ];
1251 }
1252 else
1253 {
1254 $report['complete'] = true;
1255 }
1256 }
1257
1258 return $report;
1259 }
1260
1261 private function getBorderlineUIDs(Mail\Internals\Entity\MailboxDirectory $dir): array
1262 {
1263 static $borderlineUIDs = null;
1264
1265 if (!is_null($borderlineUIDs))
1266 {
1267 return $borderlineUIDs;
1268 }
1269
1270 $error = [];
1271 $meta = $this->client->select($dir->getPath(), $error);
1272
1273 if (!isset($meta['exists']))
1274 {
1275 $this->warnings->add($this->client->getErrors()->toArray());
1276 $borderlineUIDs = [];
1277
1278 return $borderlineUIDs;
1279 }
1280
1281 $messagesNumberInTheMailService = $meta['exists'];
1282 $messages = $this->fetchMessage(sprintf('1,%u', $messagesNumberInTheMailService), $dir->getPath());
1283
1284 if (empty($messages))
1285 {
1286 $borderlineUIDs = [];
1287
1288 return $borderlineUIDs;
1289 }
1290
1291 $range = [
1292 reset($messages)['UID'],
1293 end($messages)['UID'],
1294 ];
1295
1296 sort($range);
1297
1298 //fixed errors of some mail services that give incorrect letter intervals
1299 if($range[0] === $range[1] && $messagesNumberInTheMailService > 1)
1300 {
1301 $borderlineUIDs = [];
1302
1303 return $borderlineUIDs;
1304 }
1305
1306 $borderlineUIDs = $range;
1307
1308 return $borderlineUIDs;
1309 }
1310
1312 {
1313 $borderlineUIDs = $this->getBorderlineUIDs($dir);
1314
1315 if (empty($borderlineUIDs))
1316 {
1317 return [];
1318 }
1319
1320 return [
1321 '=DIR_MD5' => $dir->getDirMd5(),
1322 [
1323 'LOGIC' => 'AND',
1324 '>=MSG_UID' => $borderlineUIDs[0],
1325 '<=MSG_UID' => $borderlineUIDs[1],
1326 ],
1327 ];
1328 }
1329
1342 private function fetchMessage(string $format, string $dirPath): array
1343 {
1344 $error = [];
1345 $messages = $this->client->fetch(false, $dirPath, $format, '(UID FLAGS)', $error);
1346
1347 if (empty($messages))
1348 {
1349 if (false === $messages)
1350 {
1351 $this->warnings->add($this->client->getErrors()->toArray());
1352 }
1353
1354 return [];
1355 }
1356
1357 krsort($messages);
1358
1359 return $messages;
1360 }
1361
1362 protected function resyncDirInternal($dir, $numberForResync = false)
1363 {
1364 $error = [];
1365 $meta = $this->client->select($dir->getPath(), $error);
1366
1367 if (!isset($meta['exists']))
1368 {
1369 $this->warnings->add($this->client->getErrors()->toArray());
1370
1371 return false;
1372 }
1373
1374 $uidtoken = $meta['uidvalidity'];
1375
1376 if ($meta['exists'] > 0)
1377 {
1378 if ($uidtoken > 0)
1379 {
1380 $result = $this->unregisterMessages(
1381 array(
1382 '=DIR_MD5' => md5($dir->getPath(true)),
1383 '<DIR_UIDV' => $uidtoken,
1384 ),
1385 [
1386 'info' => 'the directory has been deleted',
1387 ]
1388 );
1389
1390 $countDeleted = $result ? $result->getCount() : 0;
1391
1392 $this->lastSyncResult['deletedMessages'] += $countDeleted;
1393 }
1394 }
1395 else
1396 {
1397 if ($this->client->ensureEmpty($dir->getPath(), $error))
1398 {
1399 $result = $this->unregisterMessages(
1400 array(
1401 '=DIR_MD5' => md5($dir->getPath(true)),
1402 ),
1403 [
1404 'info' => 'all messages in the directory have been deleted ',
1405 ]
1406 );
1407
1408 $countDeleted = $result ? $result->getCount() : 0;
1409
1410 $this->lastSyncResult['deletedMessages'] += $countDeleted;
1411 }
1412
1413 return;
1414 }
1415
1416 $messagesNumberInTheMailService = $meta['exists'];
1417 $messages = $this->fetchMessage((($messagesNumberInTheMailService > 10000 || $numberForResync !== false) ? sprintf('1,%u', $messagesNumberInTheMailService) : '1:*'), $dir->getPath());
1418
1419 if (empty($messages))
1420 {
1421 return (false === $messages ? false : null);
1422 }
1423
1424 //interval of messages in the directory
1425 $range = array(
1426 reset($messages)['UID'],
1427 end($messages)['UID'],
1428 );
1429 sort($range);
1430
1431 //fixed errors of some mail services that give incorrect letter intervals
1432 if($range[0] === $range[1] && $messagesNumberInTheMailService > 1)
1433 {
1434 return false;
1435 }
1436
1437 //deleting non-existent messages in the service ( not included in the message interval on the service )
1438 $result = $this->unregisterMessages(
1439 array(
1440 '=DIR_MD5' => md5($dir->getPath(true)),
1441 '>MSG_UID' => 0,
1442 array(
1443 'LOGIC' => 'OR',
1444 '<MSG_UID' => $range[0],
1445 '>MSG_UID' => $range[1],
1446 ),
1447 ),
1448 [
1449 'info' => 'optimized deletion of non-existent messages',
1450 ]
1451 );
1452
1453 $countDeleted = $result ? $result->getCount() : 0;
1454
1455 $this->lastSyncResult['deletedMessages'] += $countDeleted;
1456
1457 //resynchronizing a certain number of messages
1458 if($numberForResync !== false)
1459 {
1460 $range1 = $meta['exists'];
1461 $range0 = max($range1 - ($numberForResync - 1), 1);
1462 $messages = $this->fetchMessage(sprintf('%u:%u', $range0, $range1), $dir->getPath());
1463
1464 if (empty($messages))
1465 {
1466 return;
1467 }
1468
1469 $this->resyncMessages($dir->getPath(true), $uidtoken, $messages);
1470
1471 return;
1472 }
1473
1474 if (!($meta['exists'] > 10000))
1475 {
1476 $this->resyncMessages($dir->getPath(true), $uidtoken, $messages);
1477
1478 return;
1479 }
1480
1481 $range1 = $meta['exists'];
1482 while ($range1 > 0)
1483 {
1484 $rangeSize = $range1 > 10000 ? 8000 : $range1;
1485 $range0 = max($range1 - $rangeSize, 1);
1486
1487 $messages = $this->fetchMessage(sprintf('%u:%u', $range0, $range1), $dir->getPath());
1488
1489 if (empty($messages))
1490 {
1491 return;
1492 }
1493
1494 $this->resyncMessages($dir->getPath(true), $uidtoken, $messages);
1495
1496 if ($this->isTimeQuotaExceeded())
1497 {
1498 return;
1499 }
1500
1501 $range1 -= $rangeSize;
1502 }
1503 }
1504
1505 protected function parseHeaders(&$messages)
1506 {
1507 foreach ($messages as $id => $item)
1508 {
1509 $messages[$id]['__header'] = \CMailMessage::parseHeader($item['BODY[HEADER]'], $this->mailbox['LANG_CHARSET']);
1510 $messages[$id]['__from'] = array_unique(array_map(
1511 'mb_strtolower',
1512 array_filter(
1513 array_merge(
1514 \CMailUtil::extractAllMailAddresses($messages[$id]['__header']->getHeader('FROM')),
1515 \CMailUtil::extractAllMailAddresses($messages[$id]['__header']->getHeader('REPLY-TO'))
1516 ),
1517 'trim'
1518 )
1519 ));
1520 }
1521 }
1522
1523 protected function blacklistMessages($dirPath, &$messages)
1524 {
1525 $trashDir = $this->getDirsHelper()->getTrashPath();
1526 $spamDir = $this->getDirsHelper()->getSpamPath();
1527
1528 $targetDir = $spamDir ?: $trashDir ?: null;
1529 $dir = $this->getDirsHelper()->getDirByPath($dirPath);
1530
1531 if (empty($targetDir) || ($dir && ($dir->isTrash() || $dir->isSpam())))
1532 {
1533 return;
1534 }
1535
1536 $blacklist = array(
1537 'email' => array(),
1538 'domain' => array(),
1539 );
1540
1541 $blacklistEmails = Mail\BlacklistTable::query()
1542 ->addSelect('*')
1543 ->setFilter(array(
1544 '=SITE_ID' => $this->mailbox['LID'],
1545 array(
1546 'LOGIC' => 'OR',
1547 '=MAILBOX_ID' => $this->mailbox['ID'],
1548 array(
1549 '=MAILBOX_ID' => 0,
1550 '@USER_ID' => array(0, $this->mailbox['USER_ID']),
1551 ),
1552 ),
1553 ))
1554 ->exec()
1555 ->fetchCollection();
1556 foreach ($blacklistEmails as $blacklistEmail)
1557 {
1558 if ($blacklistEmail->isDomainType())
1559 {
1560 $blacklist['domain'][] = $blacklistEmail;
1561 }
1562 else
1563 {
1564 $blacklist['email'][] = $blacklistEmail;
1565 }
1566 }
1567
1568 if (empty($blacklist['email']) && empty($blacklist['domain']))
1569 {
1570 return;
1571 }
1572
1573 $targetMessages = [];
1574 $emailAddresses = array_map(function ($element)
1575 {
1577 return $element->getItemValue();
1578 }, $blacklist['email']);
1579 $domains = array_map(function ($element)
1580 {
1582 return $element->getItemValue();
1583 }, $blacklist['domain']);
1584
1585 foreach ($messages as $id => $item)
1586 {
1587 if (!empty($blacklist['email']))
1588 {
1589 if (array_intersect($messages[$id]['__from'], $emailAddresses))
1590 {
1591 $targetMessages[$id] = $item['UID'];
1592
1593 continue;
1594 }
1595 else
1596 {
1597 foreach ($blacklist['email'] as $blacklistMail)
1598 {
1600 if (array_intersect($messages[$id]['__from'], [$blacklistMail->convertDomainToPunycode()]))
1601 {
1602 $targetMessages[$id] = $item['UID'];
1603 continue;
1604 }
1605 }
1606 }
1607 }
1608
1609 if (!empty($blacklist['domain']))
1610 {
1611 foreach ($messages[$id]['__from'] as $email)
1612 {
1613 $domain = mb_substr($email, mb_strrpos($email, '@'));
1614 if (in_array($domain, $domains))
1615 {
1616 $targetMessages[$id] = $item['UID'];
1617
1618 continue 2;
1619 }
1620 }
1621 }
1622 }
1623
1624 if (!empty($targetMessages))
1625 {
1626 if ($this->client->moveMails($targetMessages, $dirPath, $targetDir)->isSuccess())
1627 {
1628 $messages = array_diff_key($messages, $targetMessages);
1629 }
1630 }
1631 }
1632
1633 protected function buildMessageIdForDataBase($dirPath, $uidToken, $UID): string
1634 {
1635 return md5(sprintf('%s:%u:%u', $dirPath, $uidToken, $UID));
1636 }
1637
1639 {
1640 return md5(sprintf(
1641 '%s:%s:%u',
1642 trim($message['BODY[HEADER]']),
1643 $message['INTERNALDATE'],
1644 $message['RFC822.SIZE']
1645 ));
1646 }
1647
1648
1649 protected function removeExistingMessagesFromSynchronizationList($dirPath, $uidToken, &$messages)
1650 {
1651 $existingMessagesId = [];
1652
1653 $range = array(
1654 reset($messages)['UID'],
1655 end($messages)['UID'],
1656 );
1657 sort($range);
1658
1659 $result = $this->listMessages(array(
1660 'select' => [
1661 'ID'
1662 ],
1663 'filter' => array(
1664 '=DIR_MD5' => md5(Emoji::encode($dirPath)),
1665 '=DIR_UIDV' => $uidToken,
1666 '>=MSG_UID' => $range[0],
1667 '<=MSG_UID' => $range[1],
1668 ),
1669 ), false);
1670
1671 while ($item = $result->fetch())
1672 {
1673 $existingMessagesId[] = $item['ID'];
1674 }
1675
1676 foreach ($messages as $id => $item)
1677 {
1678 $messageUid = $this->buildMessageIdForDataBase($dirPath, $uidToken, $item['UID']);
1679
1680 if (in_array($messageUid, $existingMessagesId))
1681 {
1682 unset($messages[$id]);
1683 continue;
1684 }
1685
1686 //We also remove duplicate messages
1687 $existingMessagesId[] = $messageUid;
1688 }
1689 }
1690
1691 protected function searchExistingMessagesByHeaderInDataBase($headerHashes)
1692 {
1693 return $this->listMessages([
1694 'select' => ['HEADER_MD5', 'MESSAGE_ID', 'DATE_INSERT'],
1695 'filter' => [
1696 '@HEADER_MD5' => $headerHashes,
1697 ],
1698 ], false);
1699 }
1700
1701 protected function searchExistingMessagesByIdInDataBase($idsForDataBase)
1702 {
1703 return $this->listMessages(array(
1704 'select' => array('ID', 'MESSAGE_ID', 'DATE_INSERT'),
1705 'filter' => array(
1706 '@ID' => array_values($idsForDataBase),
1707 ),
1708 ), false);
1709 }
1710
1712 {
1713 $hashes = [];
1714 $idsForDataBase = [];
1715
1716 foreach ($messages as $id => $item)
1717 {
1718 $hashes[$id] = $item['__fields']['HEADER_MD5'];
1719 $idsForDataBase[$id] = $item['__fields']['ID'];
1720 }
1721
1722 $hashesMap = [];
1723
1724 foreach ($hashes as $id => $hash)
1725 {
1726 if (!array_key_exists($hash, $hashesMap))
1727 {
1728 $hashesMap[$hash] = [];
1729 }
1730
1731 $hashesMap[$hash][] = $id;
1732 }
1733
1734 $existingMessages = $this->searchExistingMessagesByHeaderInDataBase(array_keys($hashesMap));
1735
1736 /*
1737 For example, Gmail's labels act like "tags".
1738 Any individual email message can have multiple labels,
1739 and thus appear under multiple dirs.
1740 */
1741 while ($item = $existingMessages->fetch())
1742 {
1743 foreach ((array)$hashesMap[$item['HEADER_MD5']] as $id)
1744 {
1745 $messages[$id]['__created'] = $item['DATE_INSERT'];
1746 $messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID'];
1747 }
1748 }
1749
1750 $existingMessages = $this->searchExistingMessagesByIdInDataBase($idsForDataBase);
1751
1752 /*
1753 To restore messages stored with "broken" directories.
1754 For example, previously, data for messages in directories containing emojis were stored incorrectly in the database.
1755 */
1756 while ($item = $existingMessages->fetch())
1757 {
1758 $id = array_search($item['ID'], $idsForDataBase);
1759 $messages[$id]['__created'] = $item['DATE_INSERT'];
1760 $messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID'];
1761 $messages[$id]['__replaces'] = $item['ID'];
1762 }
1763 }
1764
1765 protected function fillMessageFields(&$message, $dirPath, $uidToken)
1766 {
1767 $message['__internaldate'] = Main\Type\DateTime::createFromPhp(
1768 \DateTime::createFromFormat(
1769 'j-M-Y H:i:s O',
1770 ltrim(trim($message['INTERNALDATE']), '0')
1771 ) ?: new \DateTime
1772 );
1773
1774 $message['__fields'] = [
1775 'ID' => $this->buildMessageIdForDataBase($dirPath, $uidToken, $message['UID']),
1776 'DIR_MD5' => md5(Emoji::encode($dirPath)),
1777 'DIR_UIDV' => $uidToken,
1778 'MSG_UID' => $message['UID'],
1779 'INTERNALDATE' => $message['__internaldate'],
1780 'IS_SEEN' => (isset($message['FLAGS']) && preg_grep('/^ \x5c Seen $/ix', $message['FLAGS'])) ? 'Y' : 'N',
1781 'HEADER_MD5' => $this->buildMessageHeaderHashForDataBase($message),
1782 'MESSAGE_ID' => 0,
1783 ];
1784 }
1785
1786 protected function getLocalMessageIdFromHeader($message): string
1787 {
1788 if (preg_match('/X-Bitrix-Mail-Message-UID:\s*([a-f0-9]+)/i', $message['BODY[HEADER]'], $matches))
1789 {
1790 return $matches[1];
1791 }
1792
1793 return '';
1794 }
1795
1796 protected function resyncMessages($dirPath, $uidtoken, &$messages)
1797 {
1798 $excerpt = array();
1799
1800 $range = array(
1801 reset($messages)['UID'],
1802 end($messages)['UID'],
1803 );
1804 sort($range);
1805
1806 $result = $this->listMessages(array(
1807 'select' => array('ID', 'MESSAGE_ID', 'IS_SEEN'),
1808 'filter' => array(
1809 '=DIR_MD5' => md5($dirPath),
1810 '=DIR_UIDV' => $uidtoken,
1811 '>=MSG_UID' => $range[0],
1812 '<=MSG_UID' => $range[1],
1813 ),
1814 ), false);
1815
1816 while ($item = $result->fetch())
1817 {
1818 $item['MAILBOX_USER_ID'] = $this->mailbox['USER_ID'];
1819 $excerpt[$item['ID']] = $item;
1820 }
1821
1822 $update = array(
1823 'Y' => array(),
1824 'N' => array(),
1825 'S' => array(),
1826 'U' => array(),
1827 );
1828
1829 foreach ($messages as $id => $item)
1830 {
1831 $messageUid = md5(sprintf('%s:%u:%u', $dirPath, $uidtoken, $item['UID']));
1832
1833 if (array_key_exists($messageUid, $excerpt))
1834 {
1835 $excerptSeen = $excerpt[$messageUid]['IS_SEEN'];
1836 $excerptSeenYN = in_array($excerptSeen, array('Y', 'S')) ? 'Y' : 'N';
1837 $messageSeen = preg_grep('/^ \x5c Seen $/ix', $item['FLAGS']) ? 'Y' : 'N';
1838
1839 if ($messageSeen != $excerptSeen)
1840 {
1841 if (in_array($excerptSeen, array('S', 'U')))
1842 {
1843 $excerpt[$messageUid]['IS_SEEN'] = $excerptSeenYN;
1844 $update[$excerptSeenYN][$messageUid] = $excerpt[$messageUid];
1845
1846 if ($messageSeen != $excerptSeenYN)
1847 {
1848 $update[$excerptSeen][] = $item['UID'];
1849 }
1850 }
1851 else
1852 {
1853 $excerpt[$messageUid]['IS_SEEN'] = $messageSeen;
1854 $update[$messageSeen][$messageUid] = $excerpt[$messageUid];
1855 }
1856 }
1857
1858 unset($excerpt[$messageUid]);
1859 }
1860 else
1861 {
1862 static $cache;
1863
1864 if (!isset($cache[$this->mailbox['ID']][$dirPath][$uidtoken]))
1865 {
1866 $cache[$this->mailbox['ID']][$dirPath][$uidtoken] = [
1867 'firstLocalUID' => \Bitrix\Mail\MailMessageUidTable::getFirstLocalUID((int) $this->mailbox['ID'], $dirPath, $uidtoken),
1868 'lastLocalUID' => \Bitrix\Mail\MailMessageUidTable::getLastLocalUID((int) $this->mailbox['ID'], $dirPath, $uidtoken),
1869 ];
1870 }
1871
1872 $firstLocalUID = $cache[$this->mailbox['ID']][$dirPath][$uidtoken]['firstLocalUID'];
1873 $lastLocalUID = $cache[$this->mailbox['ID']][$dirPath][$uidtoken]['lastLocalUID'];
1874
1875 if (
1876 $firstLocalUID > 0 &&
1877 $lastLocalUID > 0 &&
1878 (int) $item['UID'] > $firstLocalUID &&
1879 (int) $item['UID'] < $lastLocalUID
1880 )
1881 {
1882 $lostMessageFields = [
1883 'ID' => $messageUid,
1884 'DIR_MD5' => md5($dirPath),
1885 'DIR_UIDV' => $uidtoken,
1886 'MSG_UID' => $item['UID'],
1887 'MAILBOX_ID' => $this->mailbox['ID'],
1888 ];
1889
1890 $this->registerMessage($lostMessageFields, messageStatus: \Bitrix\Mail\MailMessageUidTable::LOST);
1891 }
1892 }
1893 }
1894
1895 $countUpdated = 0;
1896 $countDeleted = count($excerpt);
1897
1898 foreach ($update as $seen => $items)
1899 {
1900 if (!empty($items))
1901 {
1902 if (in_array($seen, array('S', 'U')))
1903 {
1904 $method = 'S' == $seen ? 'seen' : 'unseen';
1905 $this->client->$method($items, $dirPath);
1906 }
1907 else
1908 {
1909 $countUpdated += count($items);
1910
1911 $totalValues = count($items);
1912 $offset = 0;
1913 $batchSize = 100;
1914
1915 while ($offset < $totalValues)
1916 {
1917 $batchValues = array_slice($items, $offset, $batchSize, true);
1918
1920 [
1921 '@ID' => array_keys($batchValues),
1922 ],
1923 [
1924 'IS_SEEN' => $seen,
1925 ],
1926 $items = [], // @TODO: fix lazyload in MessageEventManager::processOnMailMessageModified()
1927 );
1928
1929 $offset += $batchSize;
1930 }
1931 }
1932 }
1933 }
1934
1935 if (!empty($excerpt))
1936 {
1937 $totalValues = count($excerpt);
1938 $offset = 0;
1939 $batchSize = 100;
1940
1941 while ($offset < $totalValues)
1942 {
1943 $batchValues = array_slice($excerpt, $offset, $batchSize, true);
1944
1945 $result = $this->unregisterMessages(
1946 [
1947 '@ID' => array_keys($batchValues),
1948 '=DIR_MD5' => md5($dirPath),
1949 ],
1950 [
1951 'info' => 'deletion of non-existent messages',
1952 ]
1953 );
1954
1955 $countDeleted += $result ? $result->getCount() : 0;
1956 $offset += $batchSize;
1957 }
1958 }
1959
1960 $this->lastSyncResult['updatedMessages'] += $countUpdated;
1961 $this->lastSyncResult['deletedMessages'] += $countDeleted;
1962 }
1963
1964 protected function completeMessageSync($uid)
1965 {
1966 $result = Mail\MailMessageUidTable::update(
1967 [
1968 'ID' => $uid,
1969 'MAILBOX_ID' => $this->mailbox['ID'],
1970 ],
1971 [
1972 'IS_OLD' => 'N',
1973 ]
1974 );
1975
1976 return $result->isSuccess();
1977 }
1978
1979 protected function syncMessage($dirPath, array $message, &$hashesMap = [], $ignoreSyncFrom = false, $isOutgoing = false, $isRecovered = false)
1980 {
1981 $fields = $message['__fields'];
1982
1983 if ($fields['MESSAGE_ID'] > 0)
1984 {
1985 $hashesMap[$fields['HEADER_MD5']] = $fields['MESSAGE_ID'];
1986 }
1987 else
1988 {
1989 if (array_key_exists($fields['HEADER_MD5'], $hashesMap) && $hashesMap[$fields['HEADER_MD5']] > 0)
1990 {
1991 $fields['MESSAGE_ID'] = $hashesMap[$fields['HEADER_MD5']];
1992 }
1993 }
1994
1995 $replaces = $message['__replaces'] ?? null;
1996
1997 if ($isOutgoing || !is_null($replaces))
1998 {
1999 if (!$this->registerMessage($fields, $replaces, $isOutgoing))
2000 {
2001 return false;
2002 }
2003 }
2004 else
2005 {
2006 /*
2007 * If the message is outgoing but there is no local ID in the header,
2008 * we will prepare additional parameters to search for a local copy.
2009 */
2010
2011 $isOutgoing = ($this->getDirsHelper()->getOutcomePath() === $dirPath);
2012
2013 $idFromHeaderMessage = '';
2014
2015 if ($isOutgoing && ($message['__header'] instanceof \CMailHeader))
2016 {
2017 $idFromHeaderMessage = trim($message['__header']->GetHeader("MESSAGE-ID"), " <>");
2018 }
2019
2020 if (!$this->registerMessage($fields, isOutgoing: $isOutgoing, idFromHeaderMessage: $idFromHeaderMessage))
2021 {
2022 return false;
2023 }
2024 }
2025
2026 $minimumSyncDate = $this->getMinimumSyncDate();
2027
2028 if($minimumSyncDate !== false && !$ignoreSyncFrom && $message['__internaldate']->getTimestamp() < $this->getMinimumSyncDate())
2029 {
2030 $this->completeMessageSync($fields['ID']);
2031 return false;
2032 }
2033
2034 if (!empty($message['__created']) && !empty($this->mailbox['OPTIONS']['resync_from']))
2035 {
2036 if ($message['__created']->getTimestamp() < $this->mailbox['OPTIONS']['resync_from'])
2037 {
2038 $this->completeMessageSync($fields['ID']);
2039 return false;
2040 }
2041 }
2042
2043 if ($fields['MESSAGE_ID'] > 0)
2044 {
2045 $this->completeMessageSync($fields['ID']);
2046 return true;
2047 }
2048
2049 $messageId = 0;
2050
2051 if (!empty($message['BODYSTRUCTURE']) && !empty($message['BODY[HEADER]']))
2052 {
2053 $message['__bodystructure'] = new Mail\Imap\BodyStructure($message['BODYSTRUCTURE']);
2054
2055 $message['__parts'] = $this->downloadMessageParts(
2056 $message['__fields'],
2057 $message['__bodystructure'],
2058 $this->isSupportLazyAttachments() ? self::MESSAGE_PARTS_TEXT : self::MESSAGE_PARTS_ALL
2059 );
2060
2061 // #119474
2062 if (!$message['__bodystructure']->isMultipart())
2063 {
2064 if (is_array($message['__parts']) && !empty($message['__parts']['BODY[1]']))
2065 {
2066 $message['__parts']['BODY[1.MIME]'] = $message['BODY[HEADER]'];
2067 }
2068 }
2069 }
2070 else
2071 {
2072 // fallback
2073 $message['__parts'] = $this->downloadMessage($message['__fields']) ?: false;
2074 }
2075
2076 if (false !== $message['__parts'])
2077 {
2078 $dir = $this->getDirsHelper()->getDirByPath($dirPath);
2079
2080 $messageId = $this->cacheMessage(
2081 $message,
2082 [
2083 'timestamp' => $message['__internaldate']->getTimestamp(),
2084 'size' => $message['RFC822.SIZE'],
2085 'outcome' => in_array($this->mailbox['EMAIL'], $message['__from']),
2086 'draft' => $dir != null && $dir->isDraft() || (isset($message['FLAGS']) && preg_grep('/^ \x5c Draft $/ix', $message['FLAGS'])),
2087 'trash' => $dir != null && $dir->isTrash(),
2088 'spam' => $dir != null && $dir->isSpam(),
2089 'seen' => $fields['IS_SEEN'] == 'Y',
2090 'recovered' => $isRecovered,
2091 'hash' => $fields['HEADER_MD5'],
2092 'lazy_attachments' => $this->isSupportLazyAttachments(),
2093 'excerpt' => $fields,
2094 MailMessageTable::FIELD_SANITIZE_ON_VIEW => $this->isSupportSanitizeOnView(),
2095 ],
2096 );
2097 }
2098
2099 if ($messageId > 0)
2100 {
2101 $hashesMap[$fields['HEADER_MD5']] = $messageId;
2102
2103 $this->linkMessage($fields['ID'], $messageId);
2104 }
2105
2106 $this->completeMessageSync($fields['ID']);
2107
2108 return $messageId > 0;
2109 }
2110
2111 public function downloadAttachments(array &$excerpt)
2112 {
2113 if (empty($excerpt['MSG_UID']) || empty($excerpt['DIR_MD5']))
2114 {
2115 return false;
2116 }
2117
2118 $dirPath = $this->getDirsHelper()->getDirPathByHash($excerpt['DIR_MD5']);
2119 if (empty($dirPath))
2120 {
2121 return false;
2122 }
2123
2124 $message = $this->client->fetch(true, $dirPath, $excerpt['MSG_UID'], '(BODYSTRUCTURE)', $error);
2125 if (empty($message['BODYSTRUCTURE']))
2126 {
2127 // @TODO: fallback
2128
2129 if (false === $message)
2130 {
2131 $this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
2132 }
2133
2134 return false;
2135 }
2136
2137 if (!is_array($message['BODYSTRUCTURE']))
2138 {
2139 $this->errors = new Main\ErrorCollection(array(
2140 new Main\Error('Helper\Mailbox\Imap: Invalid BODYSTRUCTURE', 0),
2141 new Main\Error((string)$message['BODYSTRUCTURE'], -1),
2142 ));
2143 return false;
2144 }
2145
2146 $message['__bodystructure'] = new Mail\Imap\BodyStructure($message['BODYSTRUCTURE']);
2147
2148 $parts = $this->downloadMessageParts(
2149 $excerpt,
2150 $message['__bodystructure'],
2151 self::MESSAGE_PARTS_ATTACHMENT
2152 );
2153
2154 $attachments = array();
2155
2156 $message['__bodystructure']->traverse(
2157 function (Mail\Imap\BodyStructure $item) use (&$parts, &$attachments)
2158 {
2159 if ($item->isMultipart() || $item->isBodyText())
2160 {
2161 return;
2162 }
2163
2164 $attachments[] = \CMailMessage::decodeMessageBody(
2166 $parts[sprintf('BODY[%s.MIME]', $item->getNumber())],
2167 $this->mailbox['LANG_CHARSET']
2168 ),
2169 $parts[sprintf('BODY[%s]', $item->getNumber())],
2170 $this->mailbox['LANG_CHARSET']
2171 );
2172 }
2173 );
2174
2175 return $attachments;
2176 }
2177
2178 protected function cacheMessage(&$message, $params = array())
2179 {
2180 if (!is_array($message))
2181 {
2182 return parent::cacheMessage($message, $params);
2183 }
2184
2185 if (!is_array($message['__parts']))
2186 {
2187 return parent::cacheMessage($message['__parts'], $params);
2188 }
2189
2190 if (empty($message['__header']))
2191 {
2192 return false;
2193 }
2194
2195 if (empty($message['__bodystructure']) || !($message['__bodystructure'] instanceof Mail\Imap\BodyStructure))
2196 {
2197 return false;
2198 }
2199
2200 $params['log_parts'] = $message['__parts'];
2201
2202 $complete = function (&$html, &$text)
2203 {
2204 if ('' !== $html && '' === $text)
2205 {
2206 $text = html_entity_decode(
2207 htmlToTxt($html),
2208 ENT_QUOTES | ENT_HTML401,
2209 $this->mailbox['LANG_CHARSET']
2210 );
2211 }
2212 elseif ('' === $html && '' !== $text)
2213 {
2214 $html = txtToHtml($text, false, 120);
2215 }
2216 };
2217
2218 [$bodyHtml, $bodyText, $attachments] = $message['__bodystructure']->traverse(
2219 function (Mail\Imap\BodyStructure $item, &$subparts) use (&$message, &$complete)
2220 {
2221 $parts = &$message['__parts'];
2222
2223 $html = '';
2224 $text = '';
2225 $attachments = array();
2226
2227 if ($item->isMultipart())
2228 {
2229 if ('alternative' === $item->getSubtype())
2230 {
2231 foreach ($subparts as $part)
2232 {
2233 $part = $part[0];
2234
2235 if ('' !== $part[0])
2236 {
2237 $html = $part[0];
2238 }
2239
2240 if ('' !== $part[1])
2241 {
2242 $text = $part[1];
2243 }
2244
2245 if (!empty($part[2]))
2246 {
2247 $attachments = array_merge($attachments, $part[2]);
2248 }
2249 }
2250
2251 $complete($html, $text);
2252 }
2253 else
2254 {
2255 foreach ($subparts as $part)
2256 {
2257 $part = $part[0];
2258
2259 $complete($part[0], $part[1]);
2260
2261 if ('' !== $part[0] || '' !== $part[1])
2262 {
2263 $html .= $part[0] . "\r\n\r\n";
2264 $text .= $part[1] . "\r\n\r\n";
2265 }
2266
2267 $attachments = array_merge($attachments, $part[2]);
2268 }
2269 }
2270
2271 $html = trim($html);
2272 $text = trim($text);
2273 }
2274 else
2275 {
2276 if (array_key_exists(sprintf('BODY[%s]', $item->getNumber()), $parts))
2277 {
2280 $parts[sprintf('BODY[%s.MIME]', $item->getNumber())],
2281 $this->mailbox['LANG_CHARSET']
2282 ),
2283 $parts[sprintf('BODY[%s]', $item->getNumber())],
2284 $this->mailbox['LANG_CHARSET']
2285 );
2286 }
2287 else
2288 {
2289 $part = [
2290 'CONTENT-TYPE' => $item->getType() . '/' . $item->getSubtype(),
2291 'CONTENT-ID' => $item->getId(),
2292 'BODY' => '',
2293 'FILENAME' => $item->getParams()['name']
2294 ];
2295 }
2296
2297 if (!$item->isBodyText())
2298 {
2299 $attachments[] = $part;
2300 }
2301 elseif (!empty($part))
2302 {
2303 if ('html' === $item->getSubtype())
2304 {
2305 $html = $part['BODY'];
2306 }
2307 else
2308 {
2309 $text = $part['BODY'];
2310 }
2311 }
2312 }
2313
2314 return array($html, $text, $attachments);
2315 }
2316 )[0];
2317
2318 $complete($bodyHtml, $bodyText);
2319
2320 return \CMailMessage::saveMessage(
2321 $this->mailbox['ID'],
2322 $dummyBody,
2323 $message['__header'],
2324 $bodyHtml,
2325 $bodyText,
2326 $attachments,
2327 $params
2328 );
2329 }
2330
2331 public function getMinimumSyncDate()
2332 {
2333 $minimumDate = false;
2334
2335 if(!empty($this->mailbox['OPTIONS']['sync_from']))
2336 {
2337 $minimumDate = $this->mailbox['OPTIONS']['sync_from'];
2338 }
2339
2341
2342 if($syncOldLimit > 0)
2343 {
2344 $syncOldLimit = strtotime(sprintf('-%u days', $syncOldLimit));
2345
2346 /*
2347 Checking in case of changes in tariff limits
2348 */
2349 if($minimumDate === false || $minimumDate < $syncOldLimit)
2350 {
2351 $minimumDate = $syncOldLimit;
2352 }
2353 }
2354 return $minimumDate;
2355 }
2356
2357 protected function getSyncRange($dirPath, &$uidtoken, $intervalSynchronizationAttempts = 0)
2358 {
2359 $meta = $this->client->select($dirPath, $error);
2360 if (false === $meta)
2361 {
2362 $this->warnings->add($this->client->getErrors()->toArray());
2363
2364 return null;
2365 }
2366
2367 if (!($meta['exists'] > 0))
2368 {
2369 return null;
2370 }
2371
2372 $uidtoken = $meta['uidvalidity'];
2373
2374 /*
2375 The interval may be smaller if the uid of the last message
2376 in the database is close to the split point in the set of intervals
2377 */
2378 $maximumLengthSynchronizationInterval = $this->getMaximumSynchronizationLengthsOfIntervals($intervalSynchronizationAttempts);
2379
2380 $rangeGetter = function ($min, $max) use ($dirPath, $uidtoken, &$rangeGetter, $maximumLengthSynchronizationInterval)
2381 {
2382
2383 $size = $max - $min + 1;
2384
2385 $set = [];
2386 $d = $size < 1000 ? $maximumLengthSynchronizationInterval : pow(10, round(ceil(log10($size) - 0.7) / 2) * 2 - 2);
2387
2388 //Take intermediate interval values
2389 for ($i = $min; $i <= $max; $i = $i + $d)
2390 {
2391 $set[] = $i;
2392 }
2393
2394 /*
2395 The end of the expected interval should not exceed the identifier of the last message
2396 */
2397 if (count($set) > 1 && end($set) >= $max)
2398 {
2399 array_pop($set);
2400 }
2401
2402 //The last item in the set must match the last item on the service
2403 $set[] = $max;
2404
2405 //Return messages starting from the 1st existing one
2406 $set = $this->client->fetch(false, $dirPath, join(',', $set), '(UID)', $error);
2407
2408 if (empty($set))
2409 {
2410 return false;
2411 }
2412
2413 ksort($set);
2414
2415 static $uidMinInDatabase, $uidMaxInDatabase, $takeFromDown;
2416
2417 if (!isset($uidMinInDatabase, $uidMaxInDatabase, $takeFromDown))
2418 {
2419 $messagesUidBoundariesIntervalInDatabase = $this->getUidRange($dirPath, $uidtoken);
2420
2421 if ($messagesUidBoundariesIntervalInDatabase)
2422 {
2423 $uidMinInDatabase = $messagesUidBoundariesIntervalInDatabase['MIN'];
2424 $uidMaxInDatabase = $messagesUidBoundariesIntervalInDatabase['MAX'];
2425 $takeFromDown = $messagesUidBoundariesIntervalInDatabase['TAKE_FROM_DOWN'];
2426 }
2427 else
2428 {
2429 $takeFromDown = true;
2430 $uidMinInDatabase = $uidMaxInDatabase = (end($set)['UID'] + 1);
2431 }
2432 }
2433
2434 if (count($set) == 1)
2435 {
2436 $uid = reset($set)['UID'];
2437
2438 if ($uid > $uidMaxInDatabase || $uid < $uidMinInDatabase)
2439 {
2440 return array($uid, $uid);
2441 }
2442 }
2443 elseif (end($set)['UID'] > $uidMaxInDatabase)
2444 {
2445 /*
2446 Select the closest element with the largest uid
2447 from the set of messages on the service (every hundredth)
2448 to a message from the database (synchronized) with the maximum uid.
2449 */
2450 do
2451 {
2452 $max = current($set)['id'];
2453 $min = prev($set)['id'];
2454 }
2455 while (current($set)['UID'] > $uidMaxInDatabase && prev($set) && next($set));
2456
2457 if ($max - $min > $maximumLengthSynchronizationInterval)
2458 {
2459 return $rangeGetter($min, $max);
2460 }
2461 else
2462 {
2463 /*
2464 Since we upload messages "up",
2465 we know the upper ones and there is no point in "capturing" existing messages in the interval.
2466 The selection is made within the interval, so the presence of extreme messages with the specified identifiers is not necessary.
2467 + 1 / - do not include an already uploaded message
2468 */
2469 return array(
2470 max($set[$min]['UID'], $uidMaxInDatabase + 1),
2471 $set[$max]['UID'],
2472 );
2473 }
2474 }
2475 elseif (reset($set)['UID'] < $uidMinInDatabase && $takeFromDown)
2476 {
2477 do
2478 {
2479 $min = current($set)['id'];
2480 $max = next($set)['id'];
2481 }
2482 while (current($set)['UID'] < $uidMinInDatabase && next($set) && prev($set));
2483
2484 if ($max - $min > $maximumLengthSynchronizationInterval)
2485 {
2486 return $rangeGetter($min, $max);
2487 }
2488 else
2489 {
2490 /*
2491 Since we upload messages "down",
2492 we know the upper ones and there is no point in "capturing" existing messages in the interval.
2493 The selection is made within the interval, so the presence of extreme messages with the specified identifiers is not necessary.
2494 - 1 / - do not include an already uploaded message
2495 */
2496 return array(
2497 min($set[$max]['UID'], $uidMinInDatabase - 1),
2498 $set[$min]['UID'],
2499 );
2500 }
2501 }
2502
2503 return null;
2504 };
2505
2506 return $rangeGetter(1, $meta['exists']);
2507 }
2508
2509 protected function getUidRange($dirPath, $uidtoken)
2510 {
2511 $filter = array(
2512 '=DIR_MD5' => md5(Emoji::encode($dirPath)),
2513 '=DIR_UIDV' => $uidtoken,
2514 '>MSG_UID' => 0,
2515 );
2516
2517 $minimumSyncDate = $this->getMinimumSyncDate();
2518
2519 $takeFromDown = true;
2520
2521 $min = $this->listMessages(
2522 array(
2523 'select' => array(
2524 'MIN' => 'MSG_UID','INTERNALDATE'
2525 ),
2526 'filter' => $filter,
2527 'order' => array(
2528 'MSG_UID' => 'ASC',
2529 ),
2530 'limit' => 1,
2531 ),
2532 false
2533 )->fetch();
2534
2535 if(isset($min['INTERNALDATE']) && $minimumSyncDate !== false && $min['INTERNALDATE']->getTimestamp() < $minimumSyncDate)
2536 {
2537 $takeFromDown = false;
2538 }
2539
2540 $max = $this->listMessages(
2541 array(
2542 'select' => array(
2543 'MAX' => 'MSG_UID',
2544 ),
2545 'filter' => $filter,
2546 'order' => array(
2547 'MSG_UID' => 'DESC',
2548 ),
2549 'limit' => 1,
2550 ),
2551 false
2552 )->fetch();
2553
2554 if ($min && $max)
2555 {
2556 return array(
2557 'MIN' => $min['MIN'],
2558 'MAX' => $max['MAX'],
2559 'TAKE_FROM_DOWN' => $takeFromDown,
2560 );
2561 }
2562
2563 return null;
2564 }
2565
2566}
$connection
Определения actionsdefinitions.php:38
$hash
Определения ajax_redirector.php:8
if(! $messageFields||!isset($messageFields['message_id'])||!isset($messageFields['status'])||!CModule::IncludeModule("messageservice")) $messageId
Определения callback_ismscenter.php:26
static overwriteMessageHeaders(Main\Mail\Mail $message, array $headers)
Определения helper.php:779
syncDirForSpecificDay($dirPath, $internalDate)
Определения imap.php:1046
markUnseen($messages)
Определения imap.php:688
const MESSAGE_PARTS_TEXT
Определения imap.php:15
resyncDir($dirPath, $numberForResync=false)
Определения imap.php:1219
downloadAttachments(array &$excerpt)
Определения imap.php:2111
const MESSAGE_PARTS_ALL
Определения imap.php:17
getSyncStatus()
Определения imap.php:110
createMessage(Main\Mail\Mail $message, array $fields=array())
Определения imap.php:401
__construct($mailbox)
Определения imap.php:42
getUidRange($dirPath, $uidtoken)
Определения imap.php:2509
listDirs($pattern, $useDb=false)
Определения imap.php:641
getLocalMessageIdFromHeader($message)
Определения imap.php:1786
downloadMessageParts(array &$excerpt, Mail\Imap\BodyStructure $bodystructure, $flags=Imap::MESSAGE_PARTS_ALL)
Определения imap.php:507
getSyncRange($dirPath, &$uidtoken, $intervalSynchronizationAttempts=0)
Определения imap.php:2357
const MESSAGE_PARTS_ATTACHMENT
Определения imap.php:16
const MAXIMUM_SYNCHRONIZATION_LENGTHS_OF_INTERVALS
Определения imap.php:18
setIsOldStatusesLowerThan($internalDate, $dirPath, $mailboxId)
Определения imap.php:879
syncMessages($mailboxID, $dirPath, $UIDs, $isRecovered=false)
Определения imap.php:929
resyncDirInternal($dir, $numberForResync=false)
Определения imap.php:1362
getMessageInFolderFilter(Mail\Internals\Entity\MailboxDirectory $dir)
Определения imap.php:1311
buildMessageIdForDataBase($dirPath, $uidToken, $UID)
Определения imap.php:1633
downloadMessage(array &$excerpt)
Определения imap.php:482
deleteMails($messages)
Определения imap.php:732
getMaximumSynchronizationLengthsOfIntervals($num)
Определения imap.php:28
resyncMessages($dirPath, $uidtoken, &$messages)
Определения imap.php:1796
checkMessagesForExistence($dirPath='INBOX', $UIDs=[])
Определения imap.php:142
resyncIsOldStatus()
Определения imap.php:202
completeMessageSync($uid)
Определения imap.php:1964
searchExistingMessagesByHeaderInDataBase($headerHashes)
Определения imap.php:1691
uploadMessage(Main\Mail\Mail $message, array &$excerpt=null)
Определения imap.php:424
cacheMessage(&$message, $params=array())
Определения imap.php:2178
removeExistingMessagesFromSynchronizationList($dirPath, $uidToken, &$messages)
Определения imap.php:1649
moveMailsToFolder($messages, $folderTo)
Определения imap.php:716
markSeen($messages)
Определения imap.php:702
linkWithExistingMessages(&$messages)
Определения imap.php:1711
buildMessageHeaderHashForDataBase($message)
Определения imap.php:1638
getFolderToMessagesMap($messages)
Определения imap.php:670
syncMessage($dirPath, array $message, &$hashesMap=[], $ignoreSyncFrom=false, $isOutgoing=false, $isRecovered=false)
Определения imap.php:1979
parseHeaders(&$messages)
Определения imap.php:1505
getSyncStatusTotal()
Определения imap.php:56
searchExistingMessagesByIdInDataBase($idsForDataBase)
Определения imap.php:1701
syncDir($dirPath)
Определения imap.php:852
fillMessageFields(&$message, $dirPath, $uidToken)
Определения imap.php:1765
getMinimumSyncDate()
Определения imap.php:2331
syncDirInternal($dir)
Определения imap.php:1060
static setCurrentSyncDir(string $path)
Определения mailboxdirectoryhelper.php:727
linkMessage($uid, $id)
Определения mailbox.php:1312
registerMessage(&$fields, $replaces=null, $isOutgoing=false, string $idFromHeaderMessage='', $redefineInsertDate=true, string $messageStatus=\Bitrix\Mail\MailMessageUidTable::DOWNLOADED)
Определения mailbox.php:1059
updateMessagesRegistry(array $filter, array $fields, $mailData=array())
Определения mailbox.php:1185
listMessages($params=array(), $fetch=true)
Определения mailbox.php:1020
setLastSyncResult(array $data)
Определения mailbox.php:1999
isSupportLazyAttachments()
Определения mailbox.php:1730
isTimeQuotaExceeded()
Определения mailbox.php:421
unregisterMessages($filter, $eventData=[], $ignoreDeletionCheck=false)
Определения mailbox.php:1200
getDirsHelper()
Определения mailbox.php:2004
static getStartInternalDateForDir( $mailboxId, ?string $dirPath=null, ?string $dirMd5=null,)
Определения messageinternaldatehandler.php:14
const FIELD_SANITIZE_ON_VIEW
Определения mailmessage.php:34
static getFirstLocalUID(int $mailboxId, string $dirPath, string $dirUIDv)
Определения mailmessageuid.php:254
static getLastLocalUID(int $mailboxId, string $dirPath, string $dirUIDv)
Определения mailmessageuid.php:249
static getTableName()
Определения mailmessageuid.php:54
static updateSyncTime($id, $val)
Определения mailboxdirectory.php:360
Определения error.php:15
static update($primary, array $data)
Определения datamanager.php:1256
Определения emoji.php:10
static createFromPhp(\DateTime $datetime)
Определения datetime.php:232
static send()
Определения event.php:387
static decodeMessageBody($header, $body, $charset)
Определения mail.php:1531
static parseHeader($header, $charset)
Определения mail.php:1524
static extractAllMailAddresses($emails)
Определения mail.php:3106
Определения mail.php:1117
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
$entity
$uid
Определения hot_keys_act.php:8
$select
Определения iblock_catalog_list.php:194
$filter
Определения iblock_catalog_list.php:54
$name
Определения menu_edit.php:35
Определения blacklist.php:3
Определения ufield.php:9
Определения address.php:8
$email
Определения payment.php:49
$message
Определения payment.php:8
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$dir
Определения quickway.php:303
$text
Определения template_pdf.php:79
$i
Определения factura.php:643
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$messages
Определения template.php:8
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$items
Определения template.php:224
$method
Определения index.php:27
if(!Loader::includeModule('sale')) $pattern
Определения index.php:20
$matches
Определения index.php:22
$error
Определения subscription_card_product.php:20
$max
Определения template_copy.php:262
$fields
Определения yandex_run.php:501