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