*/ private AssignmentNotificator $notificator; private FiltersMatcher $filtersMatcher; private ParserFactory $parserFactory; private LinkMultipleSaver $linkMultipleSaver; private DuplicateFinder $duplicateFinder; private JobSchedulerFactory $jobSchedulerFactory; private ParentFinder $parentFinder; public function __construct( EntityManager $entityManager, Config $config, AssignmentNotificatorFactory $notificatorFactory, ParserFactory $parserFactory, LinkMultipleSaver $linkMultipleSaver, DuplicateFinder $duplicateFinder, JobSchedulerFactory $jobSchedulerFactory, ParentFinder $parentFinder ) { $this->entityManager = $entityManager; $this->config = $config; $this->parserFactory = $parserFactory; $this->linkMultipleSaver = $linkMultipleSaver; $this->duplicateFinder = $duplicateFinder; $this->jobSchedulerFactory = $jobSchedulerFactory; $this->parentFinder = $parentFinder; $this->notificator = $notificatorFactory->createByClass(Email::class); $this->filtersMatcher = new FiltersMatcher(); } public function import(Message $message, Data $data): ?Email { $assignedUserId = $data->getAssignedUserId(); $teamIdList = $data->getTeamIdList(); $userIdList = $data->getUserIdList(); $filterList = $data->getFilterList(); $folderData = $data->getFolderData(); $groupEmailFolderId = $data->getGroupEmailFolderId(); $parser = $message instanceof MessageWrapper ? ($message->getParser() ?? $this->parserFactory->create()) : $this->parserFactory->create(); /** @var Email $email */ $email = $this->entityManager->getNewEntity(Email::ENTITY_TYPE); $email->set('isBeingImported', true); $subject = ''; if ($parser->hasHeader($message, 'subject')) { $subject = $parser->getHeader($message, 'subject'); } if (!empty($subject) && is_string($subject)) { $subject = trim($subject); } if ($subject !== '0' && empty($subject)) { $subject = '(No Subject)'; } if (strlen($subject) > self::SUBJECT_MAX_LENGTH) { $subject = substr($subject, 0, self::SUBJECT_MAX_LENGTH); } $email->set('isHtml', false); $email->set('name', $subject); $email->set('status', Email::STATUS_ARCHIVED); $email->set('attachmentsIds', []); $email->set('teamsIds', $teamIdList); $email->set('groupFolderId', $groupEmailFolderId); if ($assignedUserId) { $email->set('assignedUserId', $assignedUserId); $email->addLinkMultipleId('assignedUsers', $assignedUserId); } foreach ($userIdList as $uId) { $email->addLinkMultipleId('users', $uId); } $fromAddressData = $parser->getAddressData($message, 'from'); if ($fromAddressData) { $fromString = ($fromAddressData->name ? ($fromAddressData->name . ' ') : '') . '<' . $fromAddressData->address . '>'; $email->set('fromString', $fromString); } $replyToData = $parser->getAddressData($message, 'reply-To'); if ($replyToData) { $replyToString = ($replyToData->name ? ($replyToData->name . ' ') : '') . '<' . $replyToData->address . '>'; $email->set('replyToString', $replyToString); } $fromArr = $parser->getAddressList($message, 'from'); $toArr = $parser->getAddressList($message, 'to'); $ccArr = $parser->getAddressList($message, 'cc'); $replyToArr = $parser->getAddressList($message, 'reply-To'); if (count($fromArr)) { $email->set('from', $fromArr[0]); } $email->set('to', implode(';', $toArr)); $email->set('cc', implode(';', $ccArr)); $email->set('replyTo', implode(';', $replyToArr)); $addressNameMap = $parser->getAddressNameMap($message); $email->set('addressNameMap', $addressNameMap); foreach ($folderData as $uId => $folderId) { $email->setLinkMultipleColumn('users', 'folderId', $uId, $folderId); } $matchedFilter = $this->filtersMatcher->findMatch($email, $filterList, true); if ($matchedFilter && $matchedFilter->getAction() === EmailFilter::ACTION_SKIP) { return null; } if ($matchedFilter && $matchedFilter->getAction() === EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER) { $groupEmailFolderId = $matchedFilter->getGroupEmailFolderId(); $email->set('groupFolderId', $groupEmailFolderId); } if ( $parser->hasHeader($message, 'message-Id') && $parser->getHeader($message, 'message-Id') ) { /** @var string $messageId */ $messageId = $parser->getMessageId($message); $email->set('messageId', $messageId); if ($parser->hasHeader($message, 'delivered-To')) { $email->set( 'messageIdInternal', $messageId . '-' . $parser->getHeader($message, 'delivered-To') ); } if (stripos($messageId, '@espo-system') !== false) { return null; } } if ($parser->hasHeader($message, 'date')) { try { /** @var string $dateHeaderValue */ $dateHeaderValue = $parser->getHeader($message, 'date'); $dt = new DateTime($dateHeaderValue); $dateSent = $dt ->setTimezone(new DateTimeZone('UTC')) ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $email->set('dateSent', $dateSent); } catch (Exception $e) {} } $duplicate = $this->findDuplicate($email, $message); if ($duplicate && $duplicate->getStatus() !== Email::STATUS_BEING_IMPORTED) { /** @var Email $duplicate */ $duplicate = $this->entityManager->getEntityById(Email::ENTITY_TYPE, $duplicate->getId()); $this->processDuplicate( $duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList, $groupEmailFolderId ); return $duplicate; } if (!$email->getDateSent()) { $email->set('dateSent', date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)); } if ($parser->hasHeader($message, 'delivery-Date')) { try { /** @var string $deliveryDateHeaderValue */ $deliveryDateHeaderValue = $parser->getHeader($message, 'delivery-Date'); $dt = new DateTime($deliveryDateHeaderValue); $deliveryDate = $dt ->setTimezone(new DateTimeZone('UTC')) ->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT); $email->set('deliveryDate', $deliveryDate); } catch (Exception $e) {} } $inlineAttachmentList = []; if (!$data->fetchOnlyHeader()) { $inlineAttachmentList = $parser->getInlineAttachmentList($message, $email); $matchedFilter = $this->filtersMatcher->findMatch($email, $filterList); if ($matchedFilter && $matchedFilter->getAction() === EmailFilter::ACTION_SKIP) { return null; } if ($matchedFilter && $matchedFilter->getAction() === EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER) { $groupEmailFolderId = $matchedFilter->getGroupEmailFolderId(); $email->set('groupFolderId', $groupEmailFolderId); } } else { $email->set('body', 'Not fetched. The email size exceeds the limit.'); $email->set('isHtml', false); } if ( $parser->hasHeader($message, 'in-Reply-To') && $parser->getHeader($message, 'in-Reply-To') ) { $arr = explode(' ', $parser->getHeader($message, 'in-Reply-To')); $inReplyTo = $arr[0]; if ($inReplyTo) { if ($inReplyTo[0] !== '<') { $inReplyTo = '<' . $inReplyTo . '>'; } /** @var ?Email $replied */ $replied = $this->entityManager ->getRDBRepository(Email::ENTITY_TYPE) ->where(['messageId' => $inReplyTo]) ->findOne(); if ($replied) { $email->set('repliedId', $replied->getId()); /** @var string[] $repliedTeamIdList */ $repliedTeamIdList = $replied->getLinkMultipleIdList('teams'); foreach ($repliedTeamIdList as $repliedTeamId) { $email->addLinkMultipleId('teams', $repliedTeamId); } } } } $parentFound = $this->parentFinder->find($email, $message); if ($parentFound) { $email->set('parentType', $parentFound->getEntityType()); $email->set('parentId', $parentFound->getId()); } if (!$duplicate) { $this->entityManager->getLocker()->lockExclusive(Email::ENTITY_TYPE); $duplicate = $this->findDuplicate($email, $message); if ($duplicate) { $this->entityManager->getLocker()->rollback(); if ($duplicate->getStatus() !== Email::STATUS_BEING_IMPORTED) { /** @var Email $duplicate */ $duplicate = $this->entityManager->getEntityById(Email::ENTITY_TYPE, $duplicate->getId()); $this->processDuplicate( $duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList, $groupEmailFolderId ); return $duplicate; } } } if ($duplicate) { $duplicate->set([ 'from' => $email->get('from'), 'to' => $email->get('to'), 'cc' => $email->get('cc'), 'bcc' => $email->get('bcc'), 'replyTo' => $email->get('replyTo'), 'name' => $email->get('name'), 'dateSent' => $email->get('dateSent'), 'body' => $email->get('body'), 'bodyPlain' => $email->get('bodyPlain'), 'parentType' => $email->get('parentType'), 'parentId' => $email->get('parentId'), 'isHtml' => $email->get('isHtml'), 'messageId' => $email->get('messageId'), 'fromString' => $email->get('fromString'), 'replyToString' => $email->get('replyToString'), ]); /** @var EmailRepository $emailRepository */ $emailRepository = $this->entityManager->getRDBRepository(Email::ENTITY_TYPE); $emailRepository->fillAccount($duplicate); $this->processDuplicate( $duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList, $groupEmailFolderId ); return $duplicate; } if (!$email->getMessageId()) { $email->setDummyMessageId(); } $email->set('status', Email::STATUS_BEING_IMPORTED); $this->entityManager->saveEntity($email, [ SaveOption::SKIP_ALL => true, SaveOption::KEEP_NEW => true, ]); $this->entityManager->getLocker()->commit(); if ($parentFound) { $this->processEmailWithParent($email); } $email->set('status', Email::STATUS_ARCHIVED); $this->entityManager->saveEntity($email, ['isBeingImported' => true]); foreach ($inlineAttachmentList as $attachment) { $attachment->set([ 'relatedId' => $email->getId(), 'relatedType' => Email::ENTITY_TYPE, 'field' => 'body', ]); $this->entityManager->saveEntity($attachment); } return $email; } private function processEmailWithParent(Email $email): void { $parentLink = $email->getParent(); if (!$parentLink) { return; } $parentType = $parentLink->getEntityType(); $parentId = $parentLink->getId(); $emailKeepParentTeamsEntityList = $this->config->get('emailKeepParentTeamsEntityList') ?? []; if ( !in_array($parentType, $emailKeepParentTeamsEntityList) || !$this->entityManager->hasRepository($parentType) ) { return; } $parent = $this->entityManager->getEntity($parentType, $parentId); if (!$parent) { return; } if (!$parent instanceof CoreEntity) { return; } /** @var string[] $parentTeamIdList */ $parentTeamIdList = $parent->getLinkMultipleIdList('teams'); foreach ($parentTeamIdList as $parentTeamId) { $email->addLinkMultipleId('teams', $parentTeamId); } } private function findDuplicate(Email $email, Message $message): ?Email { return $this->duplicateFinder->find($email, $message); } /** * @param string[] $userIdList * @param array $folderData * @param string[] $teamIdList */ private function processDuplicate( Email $duplicate, ?string $assignedUserId, array $userIdList, array $folderData, array $teamIdList, ?string $groupEmailFolderId ): void { /** @var EmailRepository $emailRepository */ $emailRepository = $this->entityManager->getRDBRepository(Email::ENTITY_TYPE); if ($duplicate->getStatus() == Email::STATUS_ARCHIVED) { $emailRepository->loadFromField($duplicate); $emailRepository->loadToField($duplicate); } $duplicate->loadLinkMultipleField('users'); /** @var string[] $fetchedUserIdList */ $fetchedUserIdList = $duplicate->getLinkMultipleIdList('users'); $duplicate->setLinkMultipleIdList('users', []); $processNoteAcl = false; if ($assignedUserId) { if (!in_array($assignedUserId, $fetchedUserIdList)) { $processNoteAcl = true; $duplicate->addLinkMultipleId('users', $assignedUserId); } $duplicate->addLinkMultipleId('assignedUsers', $assignedUserId); } foreach ($userIdList as $uId) { if (!in_array($uId, $fetchedUserIdList)) { $processNoteAcl = true; $duplicate->addLinkMultipleId('users', $uId); } } foreach ($folderData as $uId => $folderId) { if (!in_array($uId, $fetchedUserIdList)) { $duplicate->setLinkMultipleColumn('users', 'folderId', $uId, $folderId); continue; } $this->entityManager ->getRDBRepository(Email::ENTITY_TYPE) ->getRelation($duplicate, 'users') ->updateColumnsById($uId, ['folderId' => $folderId]); } $duplicate->set('isBeingImported', true); $emailRepository->applyUsersFilters($duplicate); $saverParams = SaverParams::create()->withRawOptions([ 'skipLinkMultipleRemove' => true, 'skipLinkMultipleUpdate' => true, ]); $this->linkMultipleSaver->process($duplicate, 'users', $saverParams); $this->linkMultipleSaver->process($duplicate, 'assignedUsers', $saverParams); if ($this->emailNotificationsEnabled()) { $this->notificator->process( $duplicate, AssignmentNotificatorParams::create()->withRawOptions(['isBeingImported' => true]) ); } /** @var string[] $fetchedTeamIdList */ $fetchedTeamIdList = $duplicate->getLinkMultipleIdList('teams'); foreach ($teamIdList as $teamId) { if (!in_array($teamId, $fetchedTeamIdList)) { $processNoteAcl = true; $this->entityManager ->getRDBRepository(Email::ENTITY_TYPE) ->getRelation($duplicate, 'teams') ->relateById($teamId); } } if ($groupEmailFolderId && !$duplicate->getGroupFolder()) { $this->entityManager ->getRDBRepository(Email::ENTITY_TYPE) ->getRelation($duplicate, 'groupFolder') ->relateById($groupEmailFolderId); } if ($duplicate->getParentType() && $processNoteAcl) { // Need to update acl fields (users and teams) // of notes related to the duplicate email. // To grant access to the user who received the email. $dt = new DateTime(); $dt->modify('+' . self::PROCESS_ACL_DELAY_PERIOD); $this->jobSchedulerFactory ->create() ->setClassName(ProcessNoteAcl::class) ->setData( JobData ::create() ->withTargetId($duplicate->getId()) ->withTargetType(Email::ENTITY_TYPE) ) ->setQueue(QueueName::Q1) ->setTime($dt) ->schedule(); } } private function emailNotificationsEnabled(): bool { return in_array( Email::ENTITY_TYPE, $this->config->get('assignmentNotificationsEntityList') ?? [] ); } }