diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index 99a3ed6433..e55adaab65 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -31,17 +31,18 @@ namespace Espo\Core\Mail; use Espo\Entities\Email; -use Espo\Core\{ - Mail\MessageWrapper, - ORM\EntityManager, - Utils\Config, - Notification\AssignmentNotificator, - Notification\AssignmentNotificatorFactory, - Notification\NotificatorParams, - FieldProcessing\Relation\LinkMultipleSaver, - FieldProcessing\SaverParams, - Job\QueueName, -}; +use Espo\ORM\EntityManager; + +use Espo\Core\Notification\AssignmentNotificator; +use Espo\Core\Notification\AssignmentNotificatorFactory; +use Espo\Core\Notification\NotificatorParams; +use Espo\Core\Mail\MessageWrapper; +use Espo\Core\Utils\Config; +use Espo\Core\FieldProcessing\Relation\LinkMultipleSaver; +use Espo\Core\FieldProcessing\SaverParams; +use Espo\Core\Job\QueueName; + +use Espo\Modules\Crm\Entities\Lead; use DateTime; use DateTimeZone; @@ -114,7 +115,7 @@ class Importer $email->set('isHtml', false); $email->set('name', $subject); - $email->set('status', 'Archived'); + $email->set('status', Email::STATUS_ARCHIVED); $email->set('attachmentsIds', []); if ($assignedUserId) { @@ -195,14 +196,18 @@ class Importer $duplicate = $this->findDuplicate($email); - if ($duplicate) { - if ($duplicate->get('status') != 'Being Imported') { - $duplicate = $this->entityManager->getEntity('Email', $duplicate->id); + if ($duplicate && $duplicate->get('status') !== Email::STATUS_BEING_IMPORTED) { + $duplicate = $this->entityManager->getEntity('Email', $duplicate->getId()); - $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList); + $this->processDuplicate( + $duplicate, + $assignedUserId, + $userIdList, + $folderData, + $teamIdList + ); - return $duplicate; - } + return $duplicate; } if ($parser->hasMessageAttribute($message, 'date')) { @@ -248,8 +253,6 @@ class Importer $email->set('isHtml', false); } - $parentFound = false; - $replied = null; if ( @@ -266,7 +269,7 @@ class Importer } $replied = $this->entityManager - ->getRepository('Email') + ->getRDBRepository('Email') ->where([ 'messageId' => $inReplyTo ]) @@ -283,114 +286,62 @@ class Importer } } + $parentFound = $this->processReferences($parser, $message, $email); + if ( - $parser->hasMessageAttribute($message, 'references') && - $parser->getMessageAttribute($message, 'references') + !$parentFound && + $replied && + $replied->get('parentId') && + $replied->get('parentType') ) { - $references = $parser->getMessageAttribute($message, 'references'); - $delimiter = ' '; + $parentEntity = $this->entityManager->getEntity($replied->get('parentType'), $replied->get('parentId')); - if (strpos($references, '>,')) { - $delimiter = ','; - } + if ($parentEntity) { + $parentFound = true; - $arr = explode($delimiter, $references); - - foreach ($arr as $reference) { - $reference = trim($reference); - $reference = str_replace(['/', '@'], " ", trim($reference, '<>')); - - $parentType = $parentId = null; - $emailSent = PHP_INT_MAX; - $number = null; - - $n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number); - - if ($n != 4) { - $n = sscanf($reference, '%s %s %d %d espo-system', $parentType, $parentId, $emailSent, $number); - } - - if ($n == 4 && $emailSent < time()) { - if (!empty($parentType) && !empty($parentId)) { - if ($parentType == 'Lead') { - $parent = $this->entityManager->getEntity('Lead', $parentId); - - if ($parent && $parent->get('status') == 'Converted') { - if ($parent->get('createdAccountId')) { - $account = $this->entityManager - ->getEntity('Account', $parent->get('createdAccountId')); - - if ($account) { - $parentType = 'Account'; - $parentId = $account->id; - } - } - else { - if ($this->config->get('b2cMode')) { - if ($parent->get('createdContactId')) { - $contact = $this->entityManager - ->getEntity('Contact', $parent->get('createdContactId')); - - if ($contact) { - $parentType = 'Contact'; - $parentId = $contact->id; - } - } - } - } - } - } - - $email->set('parentType', $parentType); - $email->set('parentId', $parentId); - - $parentFound = true; - } - } + $email->set('parentType', $replied->get('parentType')); + $email->set('parentId', $replied->get('parentId')); } } - if (!$parentFound) { - if ($replied && $replied->get('parentId') && $replied->get('parentType')) { - $parentFound = $this->entityManager - ->getEntity($replied->get('parentType'), $replied->get('parentId')); - - if ($parentFound) { - $email->set('parentType', $replied->get('parentType')); - $email->set('parentId', $replied->get('parentId')); - } - } - } if (!$parentFound) { $from = $email->get('from'); if ($from) { - $parentFound = $this->findParent($email, $from); + $parentFound = $this->findParentByAddress($email, $from); } } if (!$parentFound) { if (!empty($replyToArr)) { - $parentFound = $this->findParent($email, $replyToArr[0]); + $parentFound = $this->findParentByAddress($email, $replyToArr[0]); } } if (!$parentFound) { if (!empty($toArr)) { - $parentFound = $this->findParent($email, $toArr[0]); + $parentFound = $this->findParentByAddress($email, $toArr[0]); } } if (!$duplicate) { $this->entityManager->getLocker()->lockExclusive('Email'); - if ($duplicate = $this->findDuplicate($email)) { + $duplicate = $this->findDuplicate($email); + + if ($duplicate) { $this->entityManager->getLocker()->rollback(); - if ($duplicate->get('status') != 'Being Imported') { - $duplicate = $this->entityManager->getEntity('Email', $duplicate->id); + if ($duplicate->get('status') !== Email::STATUS_BEING_IMPORTED) { + $duplicate = $this->entityManager->getEntity('Email', $duplicate->getId()); - $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList); + $this->processDuplicate( + $duplicate, + $assignedUserId, + $userIdList, + $folderData, + $teamIdList + ); return $duplicate; } @@ -418,7 +369,13 @@ class Importer $this->entityManager->getRepository('Email')->fillAccount($duplicate); - $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamIdList); + $this->processDuplicate( + $duplicate, + $assignedUserId, + $userIdList, + $folderData, + $teamIdList + ); return $duplicate; } @@ -427,7 +384,7 @@ class Importer $email->setDummyMessageId(); } - $email->set('status', 'Being Imported'); + $email->set('status', Email::STATUS_BEING_IMPORTED); $this->entityManager->saveEntity($email, [ 'skipAll' => true, @@ -437,36 +394,16 @@ class Importer $this->entityManager->getLocker()->commit(); if ($parentFound) { - $parentType = $email->get('parentType'); - $parentId = $email->get('parentId'); - - $emailKeepParentTeamsEntityList = $this->config->get('emailKeepParentTeamsEntityList', []); - - if ($parentId && in_array($parentType, $emailKeepParentTeamsEntityList)) { - if ($this->entityManager->hasRepository($parentType)) { - - $parent = $this->entityManager->getEntity($parentType, $parentId); - - if ($parent) { - $parentTeamIdList = $parent->getLinkMultipleIdList('teams'); - - foreach ($parentTeamIdList as $parentTeamId) { - $email->addLinkMultipleId('teams', $parentTeamId); - } - } - } - } + $this->processEmailWithParent($email); } - $email->set('status', 'Archived'); + $email->set('status', Email::STATUS_ARCHIVED); - $this->entityManager->saveEntity($email, [ - 'isBeingImported' => true - ]); + $this->entityManager->saveEntity($email, ['isBeingImported' => true]); foreach ($inlineAttachmentList as $attachment) { $attachment->set([ - 'relatedId' => $email->id, + 'relatedId' => $email->getId(), 'relatedType' => 'Email', 'field' => 'body', ]); @@ -477,10 +414,143 @@ class Importer return $email; } - protected function findParent(Email $email, $emailAddress): bool + private function processEmailWithParent(Email $email): void + { + $parentType = $email->get('parentType'); + $parentId = $email->get('parentId'); + + if (!$parentId || ~$parentType) { + return; + } + + $emailKeepParentTeamsEntityList = $this->config->get('emailKeepParentTeamsEntityList') ?? []; + + if ( + !in_array($parentType, $emailKeepParentTeamsEntityList) || + !$this->entityManager->hasRepository($parentType) + ) { + return; + } + + $parent = $this->entityManager->getEntity($parentType, $parentId); + + if (!$parent) { + return; + } + + $parentTeamIdList = $parent->getLinkMultipleIdList('teams'); + + foreach ($parentTeamIdList as $parentTeamId) { + $email->addLinkMultipleId('teams', $parentTeamId); + } + } + + private function processReferences(Parser $parser, MessageWrapper $message, Email $email): bool + { + if ( + !$parser->hasMessageAttribute($message, 'references') || + !$parser->getMessageAttribute($message, 'references') + ) { + return false; + } + + $references = $parser->getMessageAttribute($message, 'references'); + + $delimiter = strpos($references, '>,') ? ',' : ' '; + + foreach (explode($delimiter, $references) as $reference) { + $reference = str_replace(['/', '@'], ' ', trim(trim($reference), '<>')); + + $parentFound = $this->processReferencesItem($email, $reference); + + if ($parentFound) { + return true; + } + } + + return false; + } + + private function processReferencesItem(Email $email, string $reference): bool + { + $parentType = null; + $parentId = null; + $number = null; + $emailSent = PHP_INT_MAX; + + $n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number); + + if ($n !== 4) { + $n = sscanf($reference, '%s %s %d %d espo-system', $parentType, $parentId, $emailSent, $number); + } + + if ($n !== 4 || $emailSent >= time()) { + return false; + } + + if (!$parentType || !$parentId) { + return false; + } + + $email->set('parentType', $parentType); + $email->set('parentId', $parentId); + + if ($parentType === Lead::ENTITY_TYPE) { + $parent = $this->entityManager->getEntity(Lead::ENTITY_TYPE, $parentId); + + if (!$parent) { + $email->set('parentType', null); + $email->set('parentId', null); + + return false; + } + + $this->processReferenceLead($email, $parent); + } + + return true; + } + + private function processReferenceLead(Email $email, Lead $lead): void + { + if ($lead->get('status') !== 'Converted') { + return; + } + + if ($lead->get('createdAccountId')) { + $account = $this->entityManager->getEntity('Account', $lead->get('createdAccountId')); + + if (!$account) { + return; + } + + $email->set('parentType', 'Account'); + $email->set('parentId', $account->getId()); + + return; + } + + if ( + $this->config->get('b2cMode') && + $lead->get('createdContactId') + ) { + $contact = $this->entityManager->getEntity('Contact', $lead->get('createdContactId')); + + if (!$contact) { + return; + } + + $email->set('parentType', 'Contact'); + $email->set('parentId', $contact->getId()); + + return; + } + } + + private function findParentByAddress(Email $email, string $emailAddress): bool { $contact = $this->entityManager - ->getRepository('Contact') + ->getRDBRepository('Contact') ->where([ 'emailAddress' => $emailAddress ]) @@ -501,7 +571,7 @@ class Importer } $account = $this->entityManager - ->getRepository('Account') + ->getRDBRepository('Account') ->where([ 'emailAddress' => $emailAddress ]) @@ -515,7 +585,7 @@ class Importer } $lead = $this->entityManager - ->getRepository('Lead') + ->getRDBRepository('Lead') ->where([ 'emailAddress' => $emailAddress ]) @@ -531,14 +601,14 @@ class Importer return false; } - protected function findDuplicate(Email $email): ?Email + private function findDuplicate(Email $email): ?Email { if (!$email->get('messageId')) { return null; } $duplicate = $this->entityManager - ->getRepository('Email') + ->getRDBRepository('Email') ->select(['id', 'status']) ->where([ 'messageId' => $email->get('messageId'), @@ -548,7 +618,7 @@ class Importer return $duplicate; } - protected function processDuplicate( + private function processDuplicate( Email $duplicate, $assignedUserId, $userIdList, @@ -556,7 +626,7 @@ class Importer $teamIdList ): void { - if ($duplicate->get('status') == 'Archived') { + if ($duplicate->get('status') == Email::STATUS_ARCHIVED) { $this->entityManager->getRepository('Email')->loadFromField($duplicate); $this->entityManager->getRepository('Email')->loadToField($duplicate); } @@ -619,11 +689,10 @@ class Importer $this->linkMultipleSaver->process($duplicate, 'users', $saverParams); $this->linkMultipleSaver->process($duplicate, 'assignedUsers', $saverParams); - $notificatorParams = NotificatorParams::create()->withRawOptions([ - 'isBeingImported' => true, - ]); - - $this->notificator->process($duplicate, $notificatorParams); + $this->notificator->process( + $duplicate, + NotificatorParams::create()->withRawOptions(['isBeingImported' => true]) + ); $fetchedTeamIdList = $duplicate->getLinkMultipleIdList('teams'); diff --git a/application/Espo/Entities/Email.php b/application/Espo/Entities/Email.php index b551045487..cc9d005579 100644 --- a/application/Espo/Entities/Email.php +++ b/application/Espo/Entities/Email.php @@ -38,6 +38,10 @@ class Email extends Entity { public const ENTITY_TYPE = 'Email'; + public const STATUS_BEING_IMPORTED = 'Being Imported'; + public const STATUS_ARCHIVED = 'Archived'; + public const STATUS_SENT = 'Sent'; + protected function _getSubject() { return $this->get('name');