Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
mailbox.php
1<?php
2
3namespace Bitrix\Mail\Helper;
4
14
15abstract class Mailbox
16{
17 const SYNC_TIMEOUT = 300;
18 const SYNC_TIME_QUOTA = 280;
22
24 'yandex',
25 'mail.ru',
26 ];
27
29 protected $mailbox;
30 protected $dirsHelper;
31 protected $filters;
32 protected $session;
34 protected $syncParams = [];
35 protected $errors, $warnings;
36 protected $lastSyncResult = [
37 'newMessages' => 0,
38 'newMessagesNotify' => 0,
39 'deletedMessages' => 0,
40 'updatedMessages' => 0,
41 'newMessageId' => null,
42 ];
43
44 public static function isRuZone(): bool
45 {
46 return Loader::includeModule('bitrix24')
47 ? in_array(\CBitrix24::getPortalZone(), ['ru', 'kz', 'by'])
48 : in_array(LANGUAGE_ID, ['ru', 'kz', 'by']);
49 }
50
51 public static function getServices(): array
52 {
53 $res = MailServicesTable::getList([
54 'filter' => [
55 '=ACTIVE' => 'Y',
56 '=SITE_ID' => SITE_ID,
57 ],
58 'order' => [
59 'SORT' => 'ASC',
60 'NAME' => 'ASC',
61 ],
62 ]);
63
64 $services = [];
65
66 while ($service = $res->fetch())
67 {
68 if(!self::isRuZone() && in_array($service['NAME'], self::MAIL_SERVICES_ONLY_FOR_THE_RU_ZONE, true))
69 {
70 continue;
71 }
72
73 $serviceFinal = [
74 'id' => $service['ID'],
75 'type' => $service['SERVICE_TYPE'],
76 'name' => $service['NAME'],
77 'link' => $service['LINK'],
78 'icon' => MailServicesTable::getIconSrc($service['NAME'], $service['ICON']),
79 'server' => $service['SERVER'],
80 'port' => $service['PORT'],
81 'encryption' => $service['ENCRYPTION'],
82 'token' => $service['TOKEN'],
83 'flags' => $service['FLAGS'],
84 'sort' => $service['SORT']
85 ];
86
87 $services[] = $serviceFinal;
88 }
89
90 return $services;
91 }
92
101 public static function createInstance($id, $throw = true): Mailbox|bool
102 {
103 return static::rawInstance(array('=ID' => (int) $id, '=ACTIVE' => 'Y'), $throw);
104 }
105
106 public function getDirsMd5WithCounter($mailboxId)
107 {
108 if($this->dirsMd5WithCounter)
109 {
110 return $this->dirsMd5WithCounter;
111 }
112
113 $countersById = [];
114 $counterResult = Mail\Internals\MailCounterTable::getList([
115 'select' => [
116 'DIRECTORY_ID' => 'ENTITY_ID',
117 'UNSEEN' => 'VALUE',
118 ],
119 'filter' => [
120 '=ENTITY_TYPE' => 'DIR',
121 '=MAILBOX_ID' => $mailboxId,
122 ],
123 ]);
124 while ($item = $counterResult->fetch()) {
125 $countersById[(int)$item['DIRECTORY_ID']] = (int)$item['UNSEEN'];
126 }
127
128 if (empty($countersById)) {
129 return [];
130 }
131
132 $directoriesWithCounter = [];
133 $res = Mail\Internals\MailboxDirectoryTable::query()
134 ->whereIn('ID', array_keys($countersById))
135 ->setSelect([
136 'ID',
137 'DIR_MD5',
138 ])
139 ->where('MAILBOX_ID', $mailboxId)
140 ->exec();
141 while ($item = $res->fetch()) {
142 $id = $item['ID'];
143 $dirMd5 = $item['DIR_MD5'];
144 $directoriesWithCounter[$dirMd5] = [
145 'UNSEEN' => $countersById[$id] ?? 0,
146 'DIR_MD5' => $dirMd5,
147 ];
148 }
149
150 $this->dirsMd5WithCounter = $directoriesWithCounter;
151
152 return $directoriesWithCounter;
153 }
154
155 public function sendCountersEvent()
156 {
157 \CPullWatch::addToStack(
158 'mail_mailbox_' . $this->mailbox['ID'],
159 [
160 'params' => [
161 'dirs' => $this->getDirsWithUnseenMailCounters(),
162 ],
163 'module_id' => 'mail',
164 'command' => 'counters_is_synchronized',
165 ]
166 );
167 \Bitrix\Pull\Event::send();
168 }
169
171 {
172 global $USER;
173 $mailboxId = $this->mailbox['ID'];
174
175 if (!Helper\Message::isMailboxOwner($mailboxId, $USER->GetID()))
176 {
177 return false;
178 }
179
180 $syncDirs = $this->getDirsHelper()->getSyncDirs();
181 $defaultDirPath = $this->getDirsHelper()->getDefaultDirPath();
182 $dirs = [];
183
184 $dirsMd5WithCountOfUnseenMails = $this->getDirsMd5WithCounter($mailboxId);
185
186 $defaultDirPathId = null;
187
188 foreach ($syncDirs as $dir)
189 {
190 $newDir = [];
191 $newDir['path'] = $dir->getPath(true);
192 $newDir['name'] = $dir->getName();
193 $newDir['count'] = 0;
194 $currentDirMd5WithCountsOfUnseenMails = $dirsMd5WithCountOfUnseenMails[$dir->getDirMd5()];
195
196 if ($currentDirMd5WithCountsOfUnseenMails !== null)
197 {
198 $newDir['count'] = $currentDirMd5WithCountsOfUnseenMails['UNSEEN'];
199 }
200
201 if($newDir['path'] === $defaultDirPath)
202 {
203 $defaultDirPathId = count($dirs);
204 }
205
206 $dirs[] = $newDir;
207 }
208
209 if (empty($dirs))
210 {
211 $dirs = [
212 [
213 'count' => 0,
214 'path' => $defaultDirPath,
215 'name' => $defaultDirPath,
216 ],
217 ];
218 }
219
220 //inbox always on top
221 array_unshift( $dirs, array_splice($dirs, $defaultDirPathId, 1)[0] );
222
223 return $dirs;
224 }
225
234 public static function rawInstance($filter, $throw = true)
235 {
236 try
237 {
238 $mailbox = static::prepareMailbox($filter);
239
240 return static::instance($mailbox);
241 }
242 catch (\Exception $e)
243 {
244 if ($throw)
245 {
246 throw $e;
247 }
248 else
249 {
250 return false;
251 }
252 }
253 }
254
255 protected static function instance(array $mailbox)
256 {
257 // @TODO: other SERVER_TYPE
258 $types = array(
259 'imap' => 'Bitrix\Mail\Helper\Mailbox\Imap',
260 'controller' => 'Bitrix\Mail\Helper\Mailbox\Imap',
261 'domain' => 'Bitrix\Mail\Helper\Mailbox\Imap',
262 'crdomain' => 'Bitrix\Mail\Helper\Mailbox\Imap',
263 );
264
265 if (empty($mailbox))
266 {
267 throw new Main\ObjectException('no mailbox');
268 }
269
270 if (empty($mailbox['SERVER_TYPE']) || !array_key_exists($mailbox['SERVER_TYPE'], $types))
271 {
272 throw new Main\ObjectException('unsupported mailbox type');
273 }
274
275 return new $types[$mailbox['SERVER_TYPE']]($mailbox);
276 }
277
278 public static function prepareMailbox($filter)
279 {
280 if (is_scalar($filter))
281 {
282 $filter = array('=ID' => (int) $filter);
283 }
284
285 $mailbox = Mail\MailboxTable::getList(array(
286 'filter' => $filter,
287 'select' => array('*', 'LANG_CHARSET' => 'SITE.CULTURE.CHARSET'),
288 'limit' => 1,
289 ))->fetch() ?: array();
290
291 if (!empty($mailbox))
292 {
293 if (in_array($mailbox['SERVER_TYPE'], array('controller', 'crdomain', 'domain')))
294 {
295 $result = \CMailDomain2::getImapData(); // @TODO: request controller for 'controller' and 'crdomain'
296
297 $mailbox['SERVER'] = $result['server'];
298 $mailbox['PORT'] = $result['port'];
299 $mailbox['USE_TLS'] = $result['secure'];
300 }
301
302 Mail\MailboxTable::normalizeEmail($mailbox);
303 }
304
305 return $mailbox;
306 }
307
308 public function setSyncParams(array $params = array())
309 {
310 $this->syncParams = $params;
311 }
312
313 protected function __construct($mailbox)
314 {
315 $this->startTime = time();
316 if (defined('START_EXEC_PROLOG_BEFORE_1'))
317 {
318 $startTime = 0;
319 if (is_float(START_EXEC_PROLOG_BEFORE_1))
320 {
321 $startTime = START_EXEC_PROLOG_BEFORE_1;
322 }
323 elseif (preg_match('/ (\d+)$/', START_EXEC_PROLOG_BEFORE_1, $matches))
324 {
325 $startTime = $matches[1];
326 }
327
328 if ($startTime > 0 && $this->startTime > $startTime)
329 {
330 $this->startTime = $startTime;
331 }
332 }
333
334 $this->syncTimeout = static::getTimeout();
335
336 $this->mailbox = $mailbox;
337
339
340 $this->setCheckpoint();
341
342 $this->session = md5(uniqid(''));
343 $this->errors = new Main\ErrorCollection();
344 $this->warnings = new Main\ErrorCollection();
345 }
346
347 protected function normalizeMailboxOptions()
348 {
349 if (empty($this->mailbox['OPTIONS']) || !is_array($this->mailbox['OPTIONS']))
350 {
351 $this->mailbox['OPTIONS'] = array();
352 }
353 }
354
355 public function getMailbox()
356 {
357 return $this->mailbox;
358 }
359
360 public function getMailboxId(): int
361 {
362 $mailbox = self::getMailbox();
363
364 if (isset($mailbox['ID']))
365 {
366 return (int) $mailbox['ID'];
367 }
368
369 return 0;
370 }
371
372 public function getMailboxOwnerId(): int
373 {
374 $mailbox = self::getMailbox();
375
376 if (isset($mailbox['USER_ID']))
377 {
378 return (int) $mailbox['USER_ID'];
379 }
380
381 return 0;
382 }
383
384 /*
385 Additional check that the quota has not been exceeded
386 since the actual creation of the mailbox instance in php
387 */
388 protected function isTimeQuotaExceeded()
389 {
390 return time() - $this->startTime > ceil(static::getTimeout() * 0.9);
391 }
392
393 public function setCheckpoint()
394 {
395 $this->checkpoint = time();
396 }
397
398 public function updateGlobalCounter($userId)
399 {
400 \CUserCounter::set(
401 $userId,
402 'mail_unseen',
403 Message::getCountersForUserMailboxes($userId, true),
404 $this->mailbox['LID']
405 );
406 }
407
409 {
410 $this->updateGlobalCounter($this->mailbox['USER_ID']);
411 }
412
413 private function findMessagesWithAnEmptyBody(int $count, $mailboxId)
414 {
415 $reSyncTime = (new Main\Type\DateTime())->add('- '.static::MESSAGE_RESYNCHRONIZATION_TIME.' seconds');
416
417 $ids = Mail\Internals\MailEntityOptionsTable::getList(
418 [
419 'select' => ['ENTITY_ID'],
420 'filter' =>
421 [
422 '=MAILBOX_ID' => $mailboxId,
423 '=ENTITY_TYPE' => 'MESSAGE',
424 '=PROPERTY_NAME' => 'UNSYNC_BODY',
425 '=VALUE' => 'Y',
426 '<=DATE_INSERT' => $reSyncTime,
427 ]
428 ,
429 'limit' => $count,
430 ]
431 )->fetchAll();
432
433 return array_map(
434 function ($item)
435 {
436 return $item['ENTITY_ID'];
437 },
438 $ids
439 );
440 }
441
442 //Finds completely missing messages
443 private function findIncompleteMessages(int $count)
444 {
445 $resyncTime = new Main\Type\DateTime();
446 $resyncTime->add('- '.static::MESSAGE_RESYNCHRONIZATION_TIME.' seconds');
447
448 return Mail\MailMessageUidTable::getList([
449 'select' => array(
450 'MSG_UID',
451 'DIR_MD5',
452 ),
453 'filter' => array(
454 '=MAILBOX_ID' => $this->mailbox['ID'],
455 '=MESSAGE_ID' => '0',
456 '=IS_OLD' => 'D',
457 /*We give the message time to load.
458 In order not to catch the message that are in the process of downloading*/
459 '<=DATE_INSERT' => $resyncTime,
460 ),
461 'limit' => $count,
462 ]);
463 }
464
465 private function syncIncompleteMessages($messages)
466 {
467 while ($item = $messages->fetch())
468 {
469 $dirPath = $this->getDirsHelper()->getDirPathByHash($item['DIR_MD5']);
470 $this->syncMessages($this->mailbox['ID'], $dirPath, [$item['MSG_UID']]);
471 }
472 }
473
474 public function reSyncStartPage()
475 {
476 $this->resyncDir($this->getDirsHelper()->getDefaultDirPath(),25);
477 }
478
479 public function restoringConsistency()
480 {
481 $this->syncIncompleteMessages($this->findIncompleteMessages(static::NUMBER_OF_BROKEN_MESSAGES_TO_RESYNCHRONIZE));
482 \Bitrix\Mail\Helper\Message::reSyncBody($this->mailbox['ID'],$this->findMessagesWithAnEmptyBody(static::NUMBER_OF_BROKEN_MESSAGES_TO_RESYNCHRONIZE, $this->mailbox['ID']));
483 }
484
485 public function syncCounters()
486 {
487 Helper::setMailboxUnseenCounter($this->mailbox['ID'],Helper::updateMailCounters($this->mailbox));
488
489 $usersWithAccessToMailbox = Mailbox\SharedMailboxesManager::getUserIdsWithAccessToMailbox($this->mailbox['ID']);
490
491 foreach ($usersWithAccessToMailbox as $userId)
492 {
493 $this->updateGlobalCounter($userId);
494 }
495 }
496
497 public function sync($syncCounters = true)
498 {
499 global $DB;
500
501 /*
502 Setting a new time for an attempt to synchronize the mailbox
503 through the agent for users with a free tariff
504 */
506 {
507 $this->mailbox['OPTIONS']['next_sync'] = time() + 3600 * 24;
508
509 return 0;
510 }
511
512 /*
513 Do not start synchronization if no more than static::getTimeout() have passed since the previous one
514 */
515 if (time() - $this->mailbox['SYNC_LOCK'] < static::getTimeout())
516 {
517 return 0;
518 }
519
520 $this->mailbox['SYNC_LOCK'] = time();
521
522 /*
523 Additional check that the quota has not been exceeded
524 since the actual creation of the mailbox instance in php
525 */
526 if ($this->isTimeQuotaExceeded())
527 {
528 return 0;
529 }
530
531 $this->session = md5(uniqid(''));
532
533 $this->syncOutgoing();
534 $this->restoringConsistency();
535 $this->reSyncStartPage();
536
537 $lockSql = sprintf(
538 'UPDATE b_mail_mailbox SET SYNC_LOCK = %u WHERE ID = %u AND (SYNC_LOCK IS NULL OR SYNC_LOCK < %u)',
539 $this->mailbox['SYNC_LOCK'], $this->mailbox['ID'], $this->mailbox['SYNC_LOCK'] - static::getTimeout()
540 );
541
542 /*
543 If the time record for blocking synchronization has not been added to the table,
544 we will have to abort synchronization
545 */
546 if (!$DB->query($lockSql)->affectedRowsCount())
547 {
548 return 0;
549 }
550
551 $mailboxSyncManager = new Mailbox\MailboxSyncManager($this->mailbox['USER_ID']);
552 if ($this->mailbox['USER_ID'] > 0)
553 {
554 $mailboxSyncManager->setSyncStartedData($this->mailbox['ID']);
555 }
556
557 $syncReport = $this->syncInternal();
558 $count = $syncReport['syncCount'];
559
560 if($syncReport['reSyncStatus'])
561 {
562 /*
563 When folders are successfully resynchronized,
564 allow messages that were left to be moved to be deleted
565 */
566 Mail\MailMessageUidTable::updateList(
567 [
568 '=MAILBOX_ID' => $this->mailbox['ID'],
569 '=MSG_UID' => 0,
570 '=IS_OLD' => 'M',
571 ],
572 [
573 'IS_OLD' => 'R',
574 ],
575 );
576 }
577
578 $success = $count !== false && $this->errors->isEmpty();
579
580 $syncUnlock = $this->isTimeQuotaExceeded() ? 0 : -1;
581
582 $interval = max(1, (int) $this->mailbox['PERIOD_CHECK']) * 60;
583 $syncErrors = max(0, (int) $this->mailbox['OPTIONS']['sync_errors']);
584
585 if ($count === false)
586 {
587 $syncErrors++;
588
589 $maxInterval = 3600 * 24 * 7;
590 for ($i = 1; $i < $syncErrors && $interval < $maxInterval; $i++)
591 {
592 $interval = min($interval * ($i + 1), $maxInterval);
593 }
594 }
595 else
596 {
597 $syncErrors = 0;
598
599 $interval = $syncUnlock < 0 ? $interval : min($count > 0 ? 60 : 600, $interval);
600 }
601
602 $this->mailbox['OPTIONS']['sync_errors'] = $syncErrors;
603 $this->mailbox['OPTIONS']['next_sync'] = time() + $interval;
604
605 $optionsValue = $this->mailbox['OPTIONS'];
606
607 $unlockSql = sprintf(
608 "UPDATE b_mail_mailbox SET SYNC_LOCK = %d, OPTIONS = '%s' WHERE ID = %u AND SYNC_LOCK = %u",
609 $syncUnlock,
610 $DB->forSql(serialize($optionsValue)),
611 $this->mailbox['ID'],
612 $this->mailbox['SYNC_LOCK']
613 );
614 if ($DB->query($unlockSql)->affectedRowsCount())
615 {
616 $this->mailbox['SYNC_LOCK'] = $syncUnlock;
617 }
618
620
621 $this->pushSyncStatus(
622 array(
623 'new' => $count,
624 'updated' => $lastSyncResult['updatedMessages'],
625 'deleted' => $lastSyncResult['deletedMessages'],
626 'complete' => $this->mailbox['SYNC_LOCK'] < 0,
627 ),
628 true
629 );
630
631 $this->notifyNewMessages();
632
633 if ($this->mailbox['USER_ID'] > 0)
634 {
635 $mailboxSyncManager->setSyncStatus($this->mailbox['ID'], $success, time());
636 }
637
638 if($syncCounters)
639 {
640 $this->syncCounters();
641 }
642
643 return $count;
644 }
645
646 public function getSyncStatus()
647 {
648 return -1;
649 }
650
651 protected function pushSyncStatus($params, $force = false)
652 {
653 if (Loader::includeModule('pull'))
654 {
655 $status = $this->getSyncStatus();
656
657 \CPullWatch::addToStack(
658 'mail_mailbox_' . $this->mailbox['ID'],
659 array(
660 'module_id' => 'mail',
661 'command' => 'mailbox_sync_status',
662 'params' => array_merge(
663 array(
664 'id' => $this->mailbox['ID'],
665 'status' => sprintf('%.3f', $status),
666 'sessid' => $this->syncParams['sessid'] ?? $this->session,
667 'timestamp' => microtime(true),
668 ),
669 $params
670 ),
671 )
672 );
673
674 if ($force)
675 {
676 \Bitrix\Pull\Event::send();
677 }
678 }
679 }
680
681 public function dismissOldMessages()
682 {
683 global $DB;
684
686 {
687 return true;
688 }
689
690 $startTime = time();
691
692 if (time() - $this->mailbox['SYNC_LOCK'] < static::getTimeout())
693 {
694 return false;
695 }
696
697 if ($this->isTimeQuotaExceeded())
698 {
699 return false;
700 }
701
702 $syncUnlock = $this->mailbox['SYNC_LOCK'];
703
704 $lockSql = sprintf(
705 'UPDATE b_mail_mailbox SET SYNC_LOCK = %u WHERE ID = %u AND (SYNC_LOCK IS NULL OR SYNC_LOCK < %u)',
706 $startTime, $this->mailbox['ID'], $startTime - static::getTimeout()
707 );
708 if ($DB->query($lockSql)->affectedRowsCount())
709 {
710 $this->mailbox['SYNC_LOCK'] = $startTime;
711 }
712 else
713 {
714 return false;
715 }
716
717 $result = true;
718
719 $entity = Mail\MailMessageUidTable::getEntity();
720 $connection = $entity->getConnection();
721
722 $whereConditionForOldMessages = sprintf(
723 ' (%s)',
724 ORM\Query\Query::buildFilterSql(
725 $entity,
726 array(
727 '=MAILBOX_ID' => $this->mailbox['ID'],
728 '>MESSAGE_ID' => 0,
729 '<INTERNALDATE' => Main\Type\Date::createFromTimestamp(strtotime(sprintf('-%u days', Mail\Helper\LicenseManager::getSyncOldLimit()))),
730 '!=IS_OLD' => 'Y',
731 )
732 )
733 );
734
735 $where = sprintf(
736 ' (%s) AND NOT EXISTS (SELECT 1 FROM %s WHERE (%s) AND (%s)) ',
737 ORM\Query\Query::buildFilterSql(
738 $entity,
739 array(
740 '=MAILBOX_ID' => $this->mailbox['ID'],
741 '>MESSAGE_ID' => 0,
742 '<INTERNALDATE' => Main\Type\DateTime::createFromTimestamp(strtotime(sprintf('-%u days', Mail\Helper\LicenseManager::getSyncOldLimit()))),
743 )
744 ),
745 $connection->getSqlHelper()->quote(Mail\Internals\MessageAccessTable::getTableName()),
746 ORM\Query\Query::buildFilterSql(
747 $entity,
748 array(
749 '=MAILBOX_ID' => new Main\DB\SqlExpression('?#', 'MAILBOX_ID'),
750 '=MESSAGE_ID' => new Main\DB\SqlExpression('?#', 'MESSAGE_ID'),
751 )
752 ),
753 ORM\Query\Query::buildFilterSql(
754 Mail\Internals\MessageAccessTable::getEntity(),
755 array(
756 '=ENTITY_TYPE' => array(
757 Mail\Internals\MessageAccessTable::ENTITY_TYPE_TASKS_TASK,
758 Mail\Internals\MessageAccessTable::ENTITY_TYPE_BLOG_POST,
759 ),
760 )
761 )
762 );
763
764 $sqlHelper = $connection->getSqlHelper();
765 $messageDeleteTable = $sqlHelper->quote(Mail\Internals\MessageDeleteQueueTable::getTableName());
766 $entityTable = $sqlHelper->quote($entity->getDbTableName());
767 do
768 {
769 $selectFrom = sprintf(
770 'SELECT ID, MAILBOX_ID, MESSAGE_ID FROM %s WHERE %s ORDER BY ID LIMIT 1000',
771 $entityTable,
772 $where
773 );
774 $connection->query($sqlHelper
775 ->getInsertIgnore($messageDeleteTable, ' (ID, MAILBOX_ID, MESSAGE_ID) ', "($selectFrom)"));
776
777 $connection->query(sprintf(
778 "UPDATE %s SET IS_OLD = 'Y', IS_SEEN = 'Y' WHERE %s ORDER BY ID LIMIT 1000",
779 $connection->getSqlHelper()->quote($entity->getDbTableName()),
780 $whereConditionForOldMessages
781 ));
782
783 $connection->query(sprintf(
784 'UPDATE %s SET MESSAGE_ID = 0 WHERE %s ORDER BY ID LIMIT 1000',
785 $connection->getSqlHelper()->quote($entity->getDbTableName()),
786 $where
787 ));
788
789 if ($this->isTimeQuotaExceeded() || time() - $this->checkpoint > 15)
790 {
791 $result = false;
792
793 break;
794 }
795 }
796 while ($connection->getAffectedRowsCount() >= 1000);
797
798 $unlockSql = sprintf(
799 "UPDATE b_mail_mailbox SET SYNC_LOCK = %d WHERE ID = %u AND SYNC_LOCK = %u",
800 $syncUnlock, $this->mailbox['ID'], $this->mailbox['SYNC_LOCK']
801 );
802 if ($DB->query($unlockSql)->affectedRowsCount())
803 {
804 $this->mailbox['SYNC_LOCK'] = $syncUnlock;
805 }
806
807 return $result;
808 }
809
811 {
812 global $DB;
813
814 $startTime = time();
815
816 if (time() - $this->mailbox['SYNC_LOCK'] < static::getTimeout())
817 {
818 return false;
819 }
820
821 if ($this->isTimeQuotaExceeded())
822 {
823 return false;
824 }
825
826 $syncUnlock = $this->mailbox['SYNC_LOCK'];
827
828 $lockSql = sprintf(
829 'UPDATE b_mail_mailbox SET SYNC_LOCK = %u WHERE ID = %u AND (SYNC_LOCK IS NULL OR SYNC_LOCK < %u)',
830 $startTime, $this->mailbox['ID'], $startTime - static::getTimeout()
831 );
832 if ($DB->query($lockSql)->affectedRowsCount())
833 {
834 $this->mailbox['SYNC_LOCK'] = $startTime;
835 }
836 else
837 {
838 return false;
839 }
840
841 $minSyncTime = Mail\MailboxDirectory::getMinSyncTime($this->mailbox['ID']);
842
843 Mail\MailMessageUidTable::deleteList(
844 [
845 '=MAILBOX_ID' => $this->mailbox['ID'],
846 '>DELETE_TIME' => 0,
847 /*The values in the tables are still used to delete related items (example: attachments):*/
848 '<DELETE_TIME' => $minSyncTime,
849 ],
850 [],
851 static::MESSAGE_DELETION_LIMIT_AT_A_TIME
852 );
853
854 $unlockSql = sprintf(
855 "UPDATE b_mail_mailbox SET SYNC_LOCK = %d WHERE ID = %u AND SYNC_LOCK = %u",
856 $syncUnlock, $this->mailbox['ID'], $this->mailbox['SYNC_LOCK']
857 );
858 if ($DB->query($unlockSql)->affectedRowsCount())
859 {
860 $this->mailbox['SYNC_LOCK'] = $syncUnlock;
861 }
862
863 return true;
864 }
865
866 public function cleanup()
867 {
868 do
869 {
870 $res = Mail\Internals\MessageDeleteQueueTable::getList(array(
871 'runtime' => array(
872 new ORM\Fields\Relations\Reference(
873 'MESSAGE_UID',
874 'Bitrix\Mail\MailMessageUidTable',
875 array(
876 '=this.MAILBOX_ID' => 'ref.MAILBOX_ID',
877 '=this.MESSAGE_ID' => 'ref.MESSAGE_ID',
878 )
879 ),
880 ),
881 'select' => array('MESSAGE_ID', 'UID' => 'MESSAGE_UID.ID'),
882 'filter' => array(
883 '=MAILBOX_ID' => $this->mailbox['ID'],
884 ),
885 'limit' => 100,
886 ));
887
888 $count = 0;
889 while ($item = $res->fetch())
890 {
891 $count++;
892
893 if (empty($item['UID']))
894 {
895 \CMailMessage::delete($item['MESSAGE_ID']);
896 }
897
898 Mail\Internals\MessageDeleteQueueTable::deleteList(array(
899 '=MAILBOX_ID' => $this->mailbox['ID'],
900 '=MESSAGE_ID' => $item['MESSAGE_ID'],
901 ));
902
903 if ($this->isTimeQuotaExceeded() || time() - $this->checkpoint > 60)
904 {
905 return false;
906 }
907 }
908 }
909 while ($count > 0);
910
911 return true;
912 }
913
914 protected function listMessages($params = array(), $fetch = true)
915 {
916 $filter = array(
917 '=MAILBOX_ID' => $this->mailbox['ID'],
918 );
919
920 if (!empty($params['filter']))
921 {
922 $filter = array_merge((array) $params['filter'], $filter);
923 }
924
925 $params['filter'] = $filter;
926
927 $result = Mail\MailMessageUidTable::getList($params);
928
929 return $fetch ? $result->fetchAll() : $result;
930 }
931
932 protected function registerMessage(&$fields, $replaces = null, $isOutgoing = false)
933 {
934 $now = new Main\Type\DateTime();
935
936 if (!empty($replaces))
937 {
938 /*
939 To replace the temporary id of outgoing emails with a permanent one
940 after receiving the uid from the original mail service.
941 */
942 if($isOutgoing)
943 {
944 if (!is_array($replaces))
945 {
946 $replaces = [
947 '=ID' => $replaces,
948 ];
949 }
950
951 $exists = Mail\MailMessageUidTable::getList([
952 'select' => [
953 'ID',
954 'MESSAGE_ID',
955 ],
956 'filter' => [
957 $replaces,
958 '=MAILBOX_ID' => $this->mailbox['ID'],
959 '==DELETE_TIME' => 0,
960 ],
961 ])->fetch();
962 }
963 else
964 {
965 $exists = [
966 'ID' => $replaces,
967 'MESSAGE_ID' => $fields['MESSAGE_ID'],
968 ];
969 }
970 }
971
972 if (!empty($exists))
973 {
974 $fields['MESSAGE_ID'] = $exists['MESSAGE_ID'];
975
976 $result = (bool) Mail\MailMessageUidTable::updateList(
977 array(
978 '=ID' => $exists['ID'],
979 '=MAILBOX_ID' => $this->mailbox['ID'],
980 '==DELETE_TIME' => 0,
981 ),
982 array_merge(
983 $fields,
984 array(
985 'TIMESTAMP_X' => $now,
986 )
987 ),
988 array_merge(
989 $exists,
990 array(
991 'MAILBOX_USER_ID' => $this->mailbox['USER_ID'],
992 )
993 )
994 );
995 }
996 else
997 {
998 $checkResult = new ORM\Data\AddResult();
999 $addFields = array_merge(
1000 [
1001 'MESSAGE_ID' => 0,
1002 ],
1003 $fields,
1004 [
1005 'IS_OLD' => 'D',
1006 'MAILBOX_ID' => $this->mailbox['ID'],
1007 'SESSION_ID' => $this->session,
1008 'TIMESTAMP_X' => $now,
1009 'DATE_INSERT' => $now,
1010 ]
1011 );
1012
1013 Mail\MailMessageUidTable::checkFields($checkResult, null, $addFields);
1014 if (!$checkResult->isSuccess())
1015 {
1016 return false;
1017 }
1018
1019 Mail\MailMessageUidTable::mergeData($addFields, [
1020 'MSG_UID' => $addFields['MSG_UID'],
1021 'HEADER_MD5' => $addFields['HEADER_MD5'],
1022 'SESSION_ID' => $addFields['SESSION_ID'],
1023 'TIMESTAMP_X' => $addFields['TIMESTAMP_X'],
1024 ]);
1025
1026 return true;
1027 }
1028
1029 return $result;
1030 }
1031
1032 protected function updateMessagesRegistry(array $filter, array $fields, $mailData = array())
1033 {
1034 return Mail\MailMessageUidTable::updateList(
1035 array_merge(
1036 $filter,
1037 array(
1038 '!=IS_OLD' => 'Y',
1039 '=MAILBOX_ID' => $this->mailbox['ID'],
1040 )
1041 ),
1042 $fields,
1043 $mailData
1044 );
1045 }
1046
1047 protected function unregisterMessages($filter, $eventData = [], $ignoreDeletionCheck = false)
1048 {
1049 $messageExistInTheOriginalMailbox = false;
1050 $messagesForRemove = [];
1051 $filterForCheck = [];
1052
1053 if(!$ignoreDeletionCheck)
1054 {
1055 $filterForCheck = array_merge(
1056 $filter,
1058 [
1059 '=MAILBOX_ID' => $this->mailbox['ID'],
1060 /*
1061 We check illegally deleted messages,
1062 the disappearance of which the user may notice.
1063 According to such data, it is easier to find a message
1064 in the original mailbox for diagnostics.
1065 */
1066 '!=MESSAGE_ID' => 0,
1067 ]
1068 );
1069
1070 $messagesForRemove = Mail\MailMessageUidTable::getList([
1071 'select' => [
1072 'ID',
1073 'MAILBOX_ID',
1074 'DIR_MD5',
1075 'DIR_UIDV',
1076 'MSG_UID',
1077 'INTERNALDATE',
1078 'IS_SEEN',
1079 'DATE_INSERT',
1080 'MESSAGE_ID',
1081 'IS_OLD',
1082 ],
1083 'filter' => $filterForCheck,
1084 'limit' => 100,
1085 ])->fetchAll();
1086
1087
1088 if (!empty($messagesForRemove))
1089 {
1090 if (isset($messagesForRemove[0]['DIR_MD5']))
1091 {
1092 $dirMD5 = $messagesForRemove[0]['DIR_MD5'];
1093 $dirPath = $this->getDirsHelper()->getDirPathByHash($dirMD5);
1094 $UIDs = array_map(
1095 function ($item) {
1096 return $item['MSG_UID'];
1097 },
1098 $messagesForRemove
1099 );
1100
1101 $messageExistInTheOriginalMailbox = $this->checkMessagesForExistence($dirPath, $UIDs);
1102 }
1103 }
1104 }
1105
1106 if($messageExistInTheOriginalMailbox === false)
1107 {
1108 return Mail\MailMessageUidTable::deleteListSoft(
1109 array_merge(
1110 $filter,
1111 [
1112 '=MAILBOX_ID' => $this->mailbox['ID'],
1113 ]
1114 )
1115 );
1116 }
1117 else
1118 {
1119 $messageForLog = isset($messagesForRemove[0]) ? $messagesForRemove[0] : [];
1120
1121 /*
1122 For the log, we take a message from the entire sample,
1123 which was definitely deleted by mistake.
1124 */
1125 foreach($messagesForRemove as $message)
1126 {
1127 if(isset($message['MSG_UID']) && (int)$message['MSG_UID'] === (int)$messageExistInTheOriginalMailbox)
1128 {
1129 $messageForLog = $message;
1130 break;
1131 }
1132 }
1133
1134 if(isset($messageForLog['INTERNALDATE']) && $messageForLog['INTERNALDATE'] instanceof Main\Type\DateTime)
1135 {
1136 $messageForLog['INTERNALDATE'] = $messageForLog['INTERNALDATE']->getTimestamp();
1137 }
1138 if(isset($messageForLog['DATE_INSERT']) && $messageForLog['DATE_INSERT'] instanceof Main\Type\DateTime)
1139 {
1140 $messageForLog['DATE_INSERT'] = $messageForLog['DATE_INSERT']->getTimestamp();
1141 }
1142
1143 if(isset($filterForCheck['@ID']))
1144 {
1145 $filterForCheck['@ID'] = '[hidden for the log]';
1146 }
1147
1148 AddMessage2Log(array_merge($eventData,[
1149 'filter' => $filterForCheck,
1150 'message-data' => $messageForLog,
1151 ]));
1152
1153 return false;
1154 }
1155 }
1156
1157 protected function linkMessage($uid, $id)
1158 {
1159 $result = Mail\MailMessageUidTable::update(
1160 array(
1161 'ID' => $uid,
1162 'MAILBOX_ID' => $this->mailbox['ID'],
1163 ),
1164 array(
1165 'MESSAGE_ID' => $id,
1166 )
1167 );
1168
1169 return $result->isSuccess();
1170 }
1171
1172 protected function cacheMessage(&$body, $params = array())
1173 {
1174 if (empty($params['origin']) && empty($params['replaces']))
1175 {
1176 $params['lazy_attachments'] = $this->isSupportLazyAttachments();
1177 }
1179
1180 return \CMailMessage::addMessage(
1181 $this->mailbox['ID'],
1182 $body,
1183 $this->mailbox['CHARSET'] ?: $this->mailbox['LANG_CHARSET'],
1184 $params
1185 );
1186 }
1187
1188 public function mail(array $params)
1189 {
1190 class_exists('Bitrix\Mail\Helper');
1191
1192 $message = new Mail\DummyMail($params);
1193
1194 $messageUid = $this->createMessage($message);
1195
1196 Mail\Internals\MessageUploadQueueTable::add(array(
1197 'ID' => $messageUid,
1198 'MAILBOX_ID' => $this->mailbox['ID'],
1199 ));
1200
1201 \CAgent::addAgent(
1202 sprintf(
1203 'Bitrix\Mail\Helper::syncOutgoingAgent(%u);',
1204 $this->mailbox['ID']
1205 ),
1206 'mail', 'N', 60
1207 );
1208 }
1209
1210 protected function createMessage(Main\Mail\Mail $message, array $fields = array())
1211 {
1212 $messageUid = sprintf('%x%x', time(), rand(0, 0xffffffff));
1213 $body = sprintf(
1214 '%1$s%3$s%3$s%2$s',
1215 $message->getHeaders(),
1216 $message->getBody(),
1217 $message->getMailEol()
1218 );
1219
1220 $messageId = $this->cacheMessage(
1221 $body,
1222 array(
1223 'outcome' => true,
1224 'draft' => false,
1225 'trash' => false,
1226 'spam' => false,
1227 'seen' => true,
1228 'trackable' => true,
1229 'origin' => true,
1230 )
1231 );
1232
1233 $fields = array_merge(
1234 $fields,
1235 array(
1236 'ID' => $messageUid,
1237 'INTERNALDATE' => new Main\Type\DateTime,
1238 'IS_SEEN' => 'Y',
1239 'MESSAGE_ID' => $messageId,
1240 )
1241 );
1242
1243 $this->registerMessage($fields);
1244
1245 return $messageUid;
1246 }
1247
1248 public function syncOutgoing()
1249 {
1250 $res = $this->listMessages(
1251 array(
1252 'runtime' => array(
1253 new \Bitrix\Main\Entity\ReferenceField(
1254 'UPLOAD_QUEUE',
1255 'Bitrix\Mail\Internals\MessageUploadQueueTable',
1256 array(
1257 '=this.ID' => 'ref.ID',
1258 '=this.MAILBOX_ID' => 'ref.MAILBOX_ID',
1259 ),
1260 array(
1261 'join_type' => 'INNER',
1262 )
1263 ),
1264 ),
1265 'select' => array(
1266 '*',
1267 '__' => 'MESSAGE.*',
1268 'UPLOAD_LOCK' => 'UPLOAD_QUEUE.SYNC_LOCK',
1269 'UPLOAD_STAGE' => 'UPLOAD_QUEUE.SYNC_STAGE',
1270 'UPLOAD_ATTEMPTS' => 'UPLOAD_QUEUE.ATTEMPTS',
1271 ),
1272 'filter' => array(
1273 '>=UPLOAD_QUEUE.SYNC_STAGE' => 0,
1274 '<UPLOAD_QUEUE.SYNC_LOCK' => time() - static::getTimeout(),
1275 '<UPLOAD_QUEUE.ATTEMPTS' => 5,
1276 ),
1277 'order' => array(
1278 'UPLOAD_QUEUE.SYNC_LOCK' => 'ASC',
1279 'UPLOAD_QUEUE.SYNC_STAGE' => 'ASC',
1280 'UPLOAD_QUEUE.ATTEMPTS' => 'ASC',
1281 ),
1282 ),
1283 false
1284 );
1285
1286 while ($excerpt = $res->fetch())
1287 {
1288 $n = $excerpt['UPLOAD_ATTEMPTS'] + 1;
1289 $interval = min(static::getTimeout() * pow($n, $n), 3600 * 24 * 7);
1290
1291 if ($excerpt['UPLOAD_LOCK'] > time() - $interval)
1292 {
1293 continue;
1294 }
1295
1296 $this->syncOutgoingMessage($excerpt);
1297
1298 if ($this->isTimeQuotaExceeded())
1299 {
1300 break;
1301 }
1302 }
1303 }
1304
1305 protected function syncOutgoingMessage($excerpt)
1306 {
1307 global $DB;
1308
1309 $lockSql = sprintf(
1310 "UPDATE b_mail_message_upload_queue SET SYNC_LOCK = %u, SYNC_STAGE = %u, ATTEMPTS = ATTEMPTS + 1
1311 WHERE ID = '%s' AND MAILBOX_ID = %u AND SYNC_LOCK < %u",
1312 $syncLock = time(),
1313 max(1, $excerpt['UPLOAD_STAGE']),
1314 $DB->forSql($excerpt['ID']),
1315 $excerpt['MAILBOX_ID'],
1316 $syncLock - static::getTimeout()
1317 );
1318 if (!$DB->query($lockSql)->affectedRowsCount())
1319 {
1320 return;
1321 }
1322
1323 $outgoingBody = $excerpt['__BODY_HTML'];
1324
1325 $excerpt['__files'] = Mail\Internals\MailMessageAttachmentTable::getList(array(
1326 'select' => array(
1327 'ID', 'FILE_ID', 'FILE_NAME',
1328 ),
1329 'filter' => array(
1330 '=MESSAGE_ID' => $excerpt['__ID'],
1331 ),
1332 ))->fetchAll();
1333
1334 $attachments = array();
1335 if (!empty($excerpt['__files']) && is_array($excerpt['__files']))
1336 {
1337 $hostname = \COption::getOptionString('main', 'server_name', 'localhost');
1338 if (defined('BX24_HOST_NAME') && BX24_HOST_NAME != '')
1339 {
1340 $hostname = BX24_HOST_NAME;
1341 }
1342 else if (defined('SITE_SERVER_NAME') && SITE_SERVER_NAME != '')
1343 {
1344 $hostname = SITE_SERVER_NAME;
1345 }
1346
1347 foreach ($excerpt['__files'] as $item)
1348 {
1349 $file = \CFile::makeFileArray($item['FILE_ID']);
1350
1351 $contentId = sprintf(
1352 'bxacid.%s@%s.mail',
1353 hash('crc32b', $file['external_id'].$file['size'].$file['name']),
1354 hash('crc32b', $hostname)
1355 );
1356
1357 $attachments[] = array(
1358 'ID' => $contentId,
1359 'NAME' => $item['FILE_NAME'],
1360 'PATH' => $file['tmp_name'],
1361 'CONTENT_TYPE' => $file['type'],
1362 );
1363
1364 $outgoingBody = preg_replace(
1365 sprintf('/aid:%u/i', $item['ID']),
1366 sprintf('cid:%s', $contentId),
1367 $outgoingBody
1368 );
1369 }
1370 }
1371
1372 foreach (array('FROM', 'REPLY_TO', 'TO', 'CC', 'BCC') as $field)
1373 {
1374 $field = sprintf('__FIELD_%s', $field);
1375
1376 if (mb_strlen($excerpt[$field]) == 255 && '' != $excerpt['__HEADER'] && empty($parsedHeader))
1377 {
1378 $parsedHeader = \CMailMessage::parseHeader($excerpt['__HEADER'], LANG_CHARSET);
1379
1380 $excerpt['__FIELD_FROM'] = $parsedHeader->getHeader('FROM');
1381 $excerpt['__FIELD_REPLY_TO'] = $parsedHeader->getHeader('REPLY-TO');
1382 $excerpt['__FIELD_TO'] = $parsedHeader->getHeader('TO');
1383 $excerpt['__FIELD_CC'] = $parsedHeader->getHeader('CC');
1384 $excerpt['__FIELD_BCC'] = join(', ', array_merge(
1385 (array) $parsedHeader->getHeader('X-Original-Rcpt-to'),
1386 (array) $parsedHeader->getHeader('BCC')
1387 ));
1388 }
1389
1390 $excerpt[$field] = explode(',', $excerpt[$field]);
1391
1392 foreach ($excerpt[$field] as $k => $item)
1393 {
1394 unset($excerpt[$field][$k]);
1395
1396 $address = new Main\Mail\Address($item);
1397
1398 if ($address->validate())
1399 {
1400 if ($address->getName())
1401 {
1402 $excerpt[$field][] = sprintf(
1403 '%s <%s>',
1404 sprintf('=?%s?B?%s?=', SITE_CHARSET, base64_encode($address->getName())),
1405 $address->getEmail()
1406 );
1407 }
1408 else
1409 {
1410 $excerpt[$field][] = $address->getEmail();
1411 }
1412 }
1413 }
1414
1415 $excerpt[$field] = join(', ', $excerpt[$field]);
1416 }
1417
1418 $outgoingParams = [
1419 'CHARSET' => LANG_CHARSET,
1420 'CONTENT_TYPE' => 'html',
1421 'ATTACHMENT' => $attachments,
1422 'TO' => $excerpt['__FIELD_TO'],
1423 'SUBJECT' => $excerpt['__SUBJECT'],
1424 'BODY' => $outgoingBody,
1425 'HEADER' => [
1426 'From' => $excerpt['__FIELD_FROM'],
1427 'Reply-To' => $excerpt['__FIELD_REPLY_TO'],
1428 'Cc' => $excerpt['__FIELD_CC'],
1429 'Bcc' => $excerpt['__FIELD_BCC'],
1430 'Message-Id' => sprintf('<%s>', $excerpt['__MSG_ID']),
1431 'X-Bitrix-Mail-Message-UID' => $excerpt['ID'],
1432 ],
1433 ];
1434
1435 if(isset($excerpt['__IN_REPLY_TO']))
1436 {
1437 $outgoingParams['HEADER']['In-Reply-To'] = sprintf('<%s>', $excerpt['__IN_REPLY_TO']);
1438 }
1439
1440 $context = new Main\Mail\Context();
1441 $context->setCategory(Main\Mail\Context::CAT_EXTERNAL);
1442 $context->setPriority(Main\Mail\Context::PRIORITY_NORMAL);
1443
1444 $eventManager = \Bitrix\Main\EventManager::getInstance();
1445 $eventKey = $eventManager->addEventHandler(
1446 'main',
1447 'OnBeforeMailSend',
1448 function () use (&$excerpt)
1449 {
1450 if ($excerpt['UPLOAD_STAGE'] >= 2)
1451 {
1452 return new Main\EventResult(Main\EventResult::ERROR);
1453 }
1454 }
1455 );
1456
1457 $success = Main\Mail\Mail::send(array_merge(
1458 $outgoingParams,
1459 array(
1460 'TRACK_READ' => array(
1461 'MODULE_ID' => 'mail',
1462 'FIELDS' => array('msgid' => $excerpt['__MSG_ID']),
1463 'URL_PAGE' => '/pub/mail/read.php',
1464 ),
1465 //'TRACK_CLICK' => array(
1466 // 'MODULE_ID' => 'mail',
1467 // 'FIELDS' => array('msgid' => $excerpt['__MSG_ID']),
1468 //),
1469 'CONTEXT' => $context,
1470 )
1471 ));
1472
1473 $eventManager->removeEventHandler('main', 'OnBeforeMailSend', $eventKey);
1474
1475 if ($excerpt['UPLOAD_STAGE'] < 2 && !$success)
1476 {
1477 return false;
1478 }
1479
1480 $needUpload = true;
1481 if ($context->getSmtp() && $context->getSmtp()->getFrom() == $this->mailbox['EMAIL'])
1482 {
1483 $needUpload = !in_array('deny_upload', (array) $this->mailbox['OPTIONS']['flags']);
1484 }
1485
1486 if ($needUpload)
1487 {
1488 if ($excerpt['UPLOAD_STAGE'] < 2)
1489 {
1490 Mail\Internals\MessageUploadQueueTable::update(
1491 array(
1492 'ID' => $excerpt['ID'],
1493 'MAILBOX_ID' => $excerpt['MAILBOX_ID'],
1494 ),
1495 array(
1496 'SYNC_STAGE' => 2,
1497 'ATTEMPTS' => 1,
1498 )
1499 );
1500 }
1501
1502 class_exists('Bitrix\Mail\Helper');
1503
1504 $message = new Mail\DummyMail(array_merge(
1505 $outgoingParams,
1506 array(
1507 'HEADER' => array_merge(
1508 $outgoingParams['HEADER'],
1509 array(
1510 'To' => $outgoingParams['TO'],
1511 'Subject' => $outgoingParams['SUBJECT'],
1512 )
1513 ),
1514 )
1515 ));
1516
1517 if ($this->uploadMessage($message, $excerpt))
1518 {
1519 Mail\Internals\MessageUploadQueueTable::delete(array(
1520 'ID' => $excerpt['ID'],
1521 'MAILBOX_ID' => $excerpt['MAILBOX_ID'],
1522 ));
1523 }
1524 }
1525 else
1526 {
1527 Mail\Internals\MessageUploadQueueTable::update(
1528 array(
1529 'ID' => $excerpt['ID'],
1530 'MAILBOX_ID' => $excerpt['MAILBOX_ID'],
1531 ),
1532 array(
1533 'SYNC_STAGE' => -1,
1534 'SYNC_LOCK' => 0,
1535 )
1536 );
1537 }
1538
1539 return;
1540 }
1541
1542 public function resyncMessage(array &$excerpt)
1543 {
1544 $body = $this->downloadMessage($excerpt);
1545 if (!empty($body))
1546 {
1547 return $this->cacheMessage(
1548 $body,
1549 array(
1550 'replaces' => $excerpt['ID'],
1551 )
1552 );
1553 }
1554
1555 return false;
1556 }
1557
1558 public function downloadAttachments(array &$excerpt)
1559 {
1560 $body = $this->downloadMessage($excerpt);
1561 if (!empty($body))
1562 {
1563 [,,, $attachments] = \CMailMessage::parseMessage($body, $this->mailbox['LANG_CHARSET']);
1564
1565 return $attachments;
1566 }
1567
1568 return false;
1569 }
1570
1572 {
1573 foreach ($this->getFilters() as $filter)
1574 {
1575 foreach ($filter['__actions'] as $action)
1576 {
1577 if (empty($action['LAZY_ATTACHMENTS']))
1578 {
1579 return false;
1580 }
1581 }
1582 }
1583
1584 return true;
1585 }
1586
1587 public function getFilters($force = false)
1588 {
1589 if (is_null($this->filters) || $force)
1590 {
1591 $this->filters = Mail\MailFilterTable::getList(array(
1592 'filter' => ORM\Query\Query::filter()
1593 ->where('ACTIVE', 'Y')
1594 ->where(
1595 ORM\Query\Query::filter()->logic('or')
1596 ->where('MAILBOX_ID', $this->mailbox['ID'])
1597 ->where('MAILBOX_ID', null)
1598 ),
1599 'order' => array(
1600 'SORT' => 'ASC',
1601 'ID' => 'ASC',
1602 ),
1603 ))->fetchAll();
1604
1605 foreach ($this->filters as $k => $item)
1606 {
1607 $this->filters[$k]['__actions'] = array();
1608
1609 $res = \CMailFilter::getFilterList($item['ACTION_TYPE']);
1610 while ($row = $res->fetch())
1611 {
1612 $this->filters[$k]['__actions'][] = $row;
1613 }
1614 }
1615 }
1616
1617 return $this->filters;
1618 }
1619
1623 public function resortTree($message = null)
1624 {
1625 global $DB;
1626
1627 $worker = function ($id, $msgId, &$i)
1628 {
1629 global $DB;
1630
1631 $stack = array(
1632 array(
1633 array($id, $msgId, false),
1634 ),
1635 );
1636
1637 $excerpt = array();
1638
1639 do
1640 {
1641 $level = array_pop($stack);
1642
1643 while ($level)
1644 {
1645 [$id, $msgId, $skip] = array_shift($level);
1646
1647 if (!$skip)
1648 {
1649 $excerpt[] = $id;
1650
1651 $DB->query(sprintf(
1652 'UPDATE b_mail_message SET LEFT_MARGIN = %2$u, RIGHT_MARGIN = %3$u WHERE ID = %1$u',
1653 $id, ++$i, ++$i
1654 ));
1655
1656 if (!empty($msgId))
1657 {
1658 $replies = array();
1659
1660 $res = Mail\MailMessageTable::getList(array(
1661 'select' => array(
1662 'ID',
1663 'MSG_ID',
1664 ),
1665 'filter' => array(
1666 '=MAILBOX_ID' => $this->mailbox['ID'],
1667 '=IN_REPLY_TO' => $msgId,
1668 ),
1669 'order' => array(
1670 'FIELD_DATE' => 'ASC',
1671 ),
1672 ));
1673
1674 while ($item = $res->fetch())
1675 {
1676 if (!in_array($item['ID'], $excerpt))
1677 {
1678 $replies[] = array($item['ID'], $item['MSG_ID'], false);
1679 }
1680 }
1681
1682 if ($replies)
1683 {
1684 array_unshift($level, array($id, $msgId, true));
1685
1686 array_push($stack, $level, $replies);
1687 $i--;
1688
1689 continue 2;
1690 }
1691 }
1692 }
1693 else
1694 {
1695 $DB->query(sprintf(
1696 'UPDATE b_mail_message SET RIGHT_MARGIN = %2$u WHERE ID = %1$u',
1697 $id, ++$i
1698 ));
1699 }
1700 }
1701 }
1702 while ($stack);
1703 };
1704
1705 if (!empty($message))
1706 {
1707 if (empty($message['ID']))
1708 {
1709 throw new Main\ArgumentException("Argument 'message' is not valid");
1710 }
1711
1712 $item = $DB->query(sprintf(
1713 'SELECT GREATEST(M1, M2) AS I FROM (SELECT
1714 (SELECT RIGHT_MARGIN FROM b_mail_message WHERE MAILBOX_ID = %1$u AND RIGHT_MARGIN > 0 ORDER BY LEFT_MARGIN ASC LIMIT 1) M1,
1715 (SELECT RIGHT_MARGIN FROM b_mail_message WHERE MAILBOX_ID = %1$u AND RIGHT_MARGIN > 0 ORDER BY LEFT_MARGIN DESC LIMIT 1) M2
1716 ) M',
1717 $this->mailbox['ID']
1718 ))->fetch();
1719
1720 $i = empty($item['I']) ? 0 : $item['I'];
1721
1722 $worker($message['ID'], $message['MSG_ID'], $i);
1723 }
1724 else
1725 {
1726 $DB->query(sprintf(
1727 'UPDATE b_mail_message SET LEFT_MARGIN = 0, RIGHT_MARGIN = 0 WHERE MAILBOX_ID = %u',
1728 $this->mailbox['ID']
1729 ));
1730
1731 $i = 0;
1732
1733 $res = $DB->query(sprintf(
1734 "SELECT ID, MSG_ID FROM b_mail_message M WHERE MAILBOX_ID = %u AND (
1735 IN_REPLY_TO IS NULL OR IN_REPLY_TO = '' OR NOT EXISTS (
1736 SELECT 1 FROM b_mail_message WHERE MAILBOX_ID = M.MAILBOX_ID AND MSG_ID = M.IN_REPLY_TO
1737 )
1738 )",
1739 $this->mailbox['ID']
1740 ));
1741
1742 while ($item = $res->fetch())
1743 {
1744 $worker($item['ID'], $item['MSG_ID'], $i);
1745 }
1746
1747 // crosslinked messages
1748 $query = sprintf(
1749 'SELECT ID, MSG_ID FROM b_mail_message
1750 WHERE MAILBOX_ID = %u AND LEFT_MARGIN = 0
1751 ORDER BY FIELD_DATE ASC LIMIT 1',
1752 $this->mailbox['ID']
1753 );
1754 while ($item = $DB->query($query)->fetch())
1755 {
1756 $worker($item['ID'], $item['MSG_ID'], $i);
1757 }
1758 }
1759 }
1760
1764 public function incrementTree($message)
1765 {
1766 if (empty($message['ID']))
1767 {
1768 throw new Main\ArgumentException("Argument 'message' is not valid");
1769 }
1770
1771 if (!empty($message['IN_REPLY_TO']))
1772 {
1773 $item = Mail\MailMessageTable::getList(array(
1774 'select' => array(
1775 'ID', 'MSG_ID', 'LEFT_MARGIN', 'RIGHT_MARGIN',
1776 ),
1777 'filter' => array(
1778 '=MAILBOX_ID' => $this->mailbox['ID'],
1779 '=MSG_ID' => $message['IN_REPLY_TO'],
1780 ),
1781 'order' => array(
1782 'LEFT_MARGIN' => 'ASC',
1783 ),
1784 ))->fetch();
1785
1786 if (!empty($item))
1787 {
1788 $message = $item;
1789
1790 $item = Mail\MailMessageTable::getList(array(
1791 'select' => array(
1792 'ID', 'MSG_ID',
1793 ),
1794 'filter' => array(
1795 '=MAILBOX_ID' => $this->mailbox['ID'],
1796 '<LEFT_MARGIN' => $item['LEFT_MARGIN'],
1797 '>RIGHT_MARGIN' => $item['RIGHT_MARGIN'],
1798 ),
1799 'order' => array(
1800 'LEFT_MARGIN' => 'ASC',
1801 ),
1802 'limit' => 1,
1803 ))->fetch();
1804
1805 if (!empty($item))
1806 {
1807 $message = $item;
1808 }
1809 }
1810 }
1811
1812 $this->resortTree($message);
1813 }
1814
1815 abstract public function checkMessagesForExistence($dirPath ='INBOX',$UIDs = []);
1816 abstract public function resyncIsOldStatus();
1817 abstract public function syncFirstDay();
1818 abstract protected function syncInternal();
1819 abstract public function listDirs($pattern, $useDb = false);
1820 abstract public function uploadMessage(Main\Mail\Mail $message, array &$excerpt);
1821 abstract public function downloadMessage(array &$excerpt);
1822 abstract public function syncMessages($mailboxID, $dirPath, $UIDs);
1823 abstract public function isAuthenticated();
1824
1825 public function getErrors()
1826 {
1827 return $this->errors;
1828 }
1829
1830 public function getWarnings()
1831 {
1832 return $this->warnings;
1833 }
1834
1835 public function getLastSyncResult()
1836 {
1837 return $this->lastSyncResult;
1838 }
1839
1840 protected function setLastSyncResult(array $data)
1841 {
1842 $this->lastSyncResult = array_merge($this->lastSyncResult, $data);
1843 }
1844
1845 public function getDirsHelper()
1846 {
1847 if (!$this->dirsHelper)
1848 {
1849 $this->dirsHelper = new Mail\Helper\MailboxDirectoryHelper($this->mailbox['ID']);
1850 }
1851
1852 return $this->dirsHelper;
1853 }
1854
1855 public function activateSync()
1856 {
1857 $options = $this->mailbox['OPTIONS'];
1858
1859 if (!isset($options['activateSync']) || $options['activateSync'] === true)
1860 {
1861 return false;
1862 }
1863
1864 $entity = MailboxTable::getEntity();
1865 $connection = $entity->getConnection();
1866
1867 $options['activateSync'] = true;
1868
1869 $query = sprintf(
1870 'UPDATE %s SET %s WHERE %s',
1871 $connection->getSqlHelper()->quote($entity->getDbTableName()),
1872 $connection->getSqlHelper()->prepareUpdate($entity->getDbTableName(), [
1873 'SYNC_LOCK' => 0,
1874 'OPTIONS' => serialize($options),
1875 ])[0],
1876 Query::buildFilterSql(
1877 $entity,
1878 [
1879 'ID' => $this->mailbox['ID']
1880 ]
1881 )
1882 );
1883
1884 return $connection->query($query);
1885 }
1886
1887 public function notifyNewMessages()
1888 {
1889 if (Loader::includeModule('im'))
1890 {
1892 $count = $lastSyncResult['newMessagesNotify'];
1893 $newMessageId = $lastSyncResult['newMessageId'];
1894 $message = null;
1895
1896 if ($count < 1)
1897 {
1898 return;
1899 }
1900
1901 if ($newMessageId > 0 && $count === 1)
1902 {
1903 $message = Mail\MailMessageTable::getByPrimary($newMessageId)->fetch();
1904
1905 if (!empty($message))
1906 {
1907 Mail\Helper\Message::prepare($message);
1908 }
1909 }
1910
1911 Mail\Integration\Im\Notification::add(
1912 $this->mailbox['USER_ID'],
1913 'new_message',
1914 array(
1915 'mailboxOwnerId' => $this->mailbox['USER_ID'],
1916 'mailboxId' => $this->mailbox['ID'],
1917 'count' => $count,
1918 'message' => $message,
1919 )
1920 );
1921 }
1922 }
1923
1930 public function isSupportSanitizeOnView(): bool
1931 {
1932 $supportedActionTypes = [
1933 // doesn't use BODY_HTML or BODY_BB
1934 "forumsocnet",
1935 "support",
1936 "crm",
1937 ];
1938 foreach ($this->getFilters() as $filter)
1939 {
1940 if (
1941 !in_array($filter['ACTION_TYPE'], $supportedActionTypes, true)
1942 && (
1943 $this->hasActionWithoutSanitizeSupport($filter['__actions'])
1944 || !empty($filter['PHP_CONDITION'])
1945 || !empty($filter['ACTION_PHP'])
1946 )
1947 )
1948 {
1949 return false;
1950 }
1951 }
1952 return true;
1953 }
1954
1962 private function hasActionWithoutSanitizeSupport($actions): bool
1963 {
1964 if (is_array($actions))
1965 {
1966 foreach ($actions as $action)
1967 {
1968 if (empty($action['SANITIZE_ON_VIEW']))
1969 {
1970 return true;
1971 }
1972 }
1973 }
1974 return false;
1975 }
1976
1977 /*
1978 Returns the minimum time between possible re-synchronization
1979 The time is taken from the option 'max_execution_time', but no more than static::SYNC_TIMEOUT
1980 */
1981 final public static function getTimeout()
1982 {
1983 return min(max(0, ini_get('max_execution_time')) ?: static::SYNC_TIMEOUT, static::SYNC_TIMEOUT);
1984 }
1985
1986 final public static function getForUserByEmail($email)
1987 {
1988 $mailbox = Mail\MailboxTable::getUserMailboxWithEmail($email);
1989 if (isset($mailbox['EMAIL']))
1990 {
1991 return static::createInstance($mailbox['ID'], false);
1992 }
1993
1994 return null;
1995 }
1996
1997 final public static function findBy($id, $email): ?Mailbox
1998 {
1999 $instance = null;
2000
2001 if ($id > 0)
2002 {
2004 {
2005 $instance = static::createInstance($mailbox['ID'], false);
2006 }
2007 }
2008
2009 if (!empty($email) && empty($instance))
2010 {
2011 $instance = static::getForUserByEmail($email);
2012 }
2013
2014 if (!empty($instance))
2015 {
2016 return $instance;
2017 }
2018
2019 return null;
2020 }
2021}
static checkTheMailboxForSyncAvailability(int $checkedMailboxId)
static createInstance($id, $throw=true)
Definition mailbox.php:101
downloadAttachments(array &$excerpt)
Definition mailbox.php:1558
resortTree($message=null)
Definition mailbox.php:1623
createMessage(Main\Mail\Mail $message, array $fields=array())
Definition mailbox.php:1210
getDirsMd5WithCounter($mailboxId)
Definition mailbox.php:106
listDirs($pattern, $useDb=false)
uploadMessage(Main\Mail\Mail $message, array &$excerpt)
setSyncParams(array $params=array())
Definition mailbox.php:308
registerMessage(&$fields, $replaces=null, $isOutgoing=false)
Definition mailbox.php:932
static instance(array $mailbox)
Definition mailbox.php:255
updateMessagesRegistry(array $filter, array $fields, $mailData=array())
Definition mailbox.php:1032
const MESSAGE_DELETION_LIMIT_AT_A_TIME
Definition mailbox.php:20
downloadMessage(array &$excerpt)
listMessages($params=array(), $fetch=true)
Definition mailbox.php:914
resyncMessage(array &$excerpt)
Definition mailbox.php:1542
checkMessagesForExistence($dirPath='INBOX', $UIDs=[])
setLastSyncResult(array $data)
Definition mailbox.php:1840
const NUMBER_OF_BROKEN_MESSAGES_TO_RESYNCHRONIZE
Definition mailbox.php:21
sync($syncCounters=true)
Definition mailbox.php:497
cacheMessage(&$body, $params=array())
Definition mailbox.php:1172
syncMessages($mailboxID, $dirPath, $UIDs)
static rawInstance($filter, $throw=true)
Definition mailbox.php:234
unregisterMessages($filter, $eventData=[], $ignoreDeletionCheck=false)
Definition mailbox.php:1047
static findBy($id, $email)
Definition mailbox.php:1997
const MAIL_SERVICES_ONLY_FOR_THE_RU_ZONE
Definition mailbox.php:23
static prepareMailbox($filter)
Definition mailbox.php:278
static getForUserByEmail($email)
Definition mailbox.php:1986
pushSyncStatus($params, $force=false)
Definition mailbox.php:651
static setMailboxUnseenCounter($mailboxId, $count)
Definition helper.php:506
static updateMailCounters($mailbox)
Definition helper.php:565
static updateList(array $filter, array $fields, array $eventData=[])
static getIconSrc($serviceName, $iconId=null)
static getUserMailbox($mailboxId, $userId=null)
Definition mailbox.php:110
static createFromTimestamp($timestamp)
Definition datetime.php:246