Initial commit
This commit is contained in:
42
application/Espo/Core/Mail/Importer/AutoReplyDetector.php
Normal file
42
application/Espo/Core/Mail/Importer/AutoReplyDetector.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
|
||||
/**
|
||||
* Detects if an email is auto-response.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
interface AutoReplyDetector
|
||||
{
|
||||
public function detect(Message $message): bool;
|
||||
}
|
||||
172
application/Espo/Core/Mail/Importer/Data.php
Normal file
172
application/Espo/Core/Mail/Importer/Data.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Entities\EmailFilter;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
private ?string $assignedUserId = null;
|
||||
/** @var string[] */
|
||||
private array $teamIdList = [];
|
||||
/** @var string[] */
|
||||
private array $userIdList = [];
|
||||
/** @var iterable<EmailFilter> */
|
||||
private iterable $filterList = [];
|
||||
private bool $fetchOnlyHeader = false;
|
||||
/** @var array<string, string> */
|
||||
private array $folderData = [];
|
||||
private ?string $groupEmailFolderId = null;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function getAssignedUserId(): ?string
|
||||
{
|
||||
return $this->assignedUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTeamIdList(): array
|
||||
{
|
||||
return $this->teamIdList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUserIdList(): array
|
||||
{
|
||||
return $this->userIdList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<EmailFilter>
|
||||
*/
|
||||
public function getFilterList(): iterable
|
||||
{
|
||||
return $this->filterList;
|
||||
}
|
||||
|
||||
public function fetchOnlyHeader(): bool
|
||||
{
|
||||
return $this->fetchOnlyHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getFolderData(): array
|
||||
{
|
||||
return $this->folderData;
|
||||
}
|
||||
|
||||
public function getGroupEmailFolderId(): ?string
|
||||
{
|
||||
return $this->groupEmailFolderId;
|
||||
}
|
||||
|
||||
public function withAssignedUserId(?string $assignedUserId): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->assignedUserId = $assignedUserId;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $teamIdList
|
||||
*/
|
||||
public function withTeamIdList(array $teamIdList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->teamIdList = $teamIdList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $userIdList
|
||||
*/
|
||||
public function withUserIdList(array $userIdList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->userIdList = $userIdList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<EmailFilter> $filterList
|
||||
*/
|
||||
public function withFilterList(iterable $filterList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->filterList = $filterList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withFetchOnlyHeader(bool $fetchOnlyHeader = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fetchOnlyHeader = $fetchOnlyHeader;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $folderData
|
||||
*/
|
||||
public function withFolderData(array $folderData): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->folderData = $folderData;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withGroupEmailFolderId(?string $groupEmailFolderId): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->groupEmailFolderId = $groupEmailFolderId;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
|
||||
class DefaultAutoReplyDetector implements AutoReplyDetector
|
||||
{
|
||||
public function detect(Message $message): bool
|
||||
{
|
||||
if ($message->getHeader('X-Autoreply')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($message->getHeader('X-Autorespond')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$message->getHeader('Auto-Submitted') &&
|
||||
strtolower($message->getHeader('Auto-Submitted')) === 'auto-replied'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
class DefaultDuplicateFinder implements DuplicateFinder
|
||||
{
|
||||
public function __construct(private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function find(Email $email, Message $message): ?Email
|
||||
{
|
||||
if (!$email->getMessageId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(Email::class)
|
||||
->select([Attribute::ID, 'status'])
|
||||
->where([
|
||||
'messageId' => $email->getMessageId(),
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
}
|
||||
723
application/Espo/Core/Mail/Importer/DefaultImporter.php
Normal file
723
application/Espo/Core/Mail/Importer/DefaultImporter.php
Normal file
@@ -0,0 +1,723 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Field\DateTime as DateTimeField;
|
||||
use Espo\Core\Field\LinkMultiple;
|
||||
use Espo\Core\Field\LinkParent;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
use Espo\Core\Mail\FiltersMatcher;
|
||||
use Espo\Core\Mail\Importer;
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Core\Mail\MessageWrapper;
|
||||
use Espo\Core\Mail\Parser;
|
||||
use Espo\Core\Mail\ParserFactory;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\ORM\Repository\Option\SaveOption;
|
||||
use Espo\Core\Notification\AssignmentNotificator;
|
||||
use Espo\Core\Notification\AssignmentNotificatorFactory;
|
||||
use Espo\Core\Notification\AssignmentNotificator\Params as AssignmentNotificatorParams;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\FieldProcessing\Relation\LinkMultipleSaver;
|
||||
use Espo\Core\FieldProcessing\Saver\Params as SaverParams;
|
||||
use Espo\Core\Job\QueueName;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\EmailFilter;
|
||||
use Espo\Entities\GroupEmailFolder;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Repositories\Email as EmailRepository;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Stream\Jobs\ProcessNoteAcl;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
|
||||
class DefaultImporter implements Importer
|
||||
{
|
||||
private const SUBJECT_MAX_LENGTH = 255;
|
||||
private const PROCESS_ACL_DELAY_PERIOD = '5 seconds';
|
||||
|
||||
/** @var AssignmentNotificator<Email> */
|
||||
private AssignmentNotificator $notificator;
|
||||
private FiltersMatcher $filtersMatcher;
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Config $config,
|
||||
AssignmentNotificatorFactory $notificatorFactory,
|
||||
private ParserFactory $parserFactory,
|
||||
private LinkMultipleSaver $linkMultipleSaver,
|
||||
private DuplicateFinder $duplicateFinder,
|
||||
private JobSchedulerFactory $jobSchedulerFactory,
|
||||
private ParentFinder $parentFinder,
|
||||
private AutoReplyDetector $autoReplyDetector,
|
||||
) {
|
||||
$this->notificator = $notificatorFactory->createByClass(Email::class);
|
||||
$this->filtersMatcher = new FiltersMatcher();
|
||||
}
|
||||
|
||||
public function import(Message $message, Data $data): ?Email
|
||||
{
|
||||
$parser = $this->getParser($message);
|
||||
|
||||
$email = $this->entityManager->getRDBRepositoryByClass(Email::class)->getNew();
|
||||
$email->set('isBeingImported', true);
|
||||
|
||||
$subject = $this->getSubject($parser, $message);
|
||||
|
||||
$email
|
||||
->setSubject($subject)
|
||||
->setStatus(Email::STATUS_ARCHIVED)
|
||||
->setIsHtml(false)
|
||||
->setGroupFolderId($data->getGroupEmailFolderId())
|
||||
->setTeams(LinkMultiple::create()->withAddedIdList($data->getTeamIdList()));
|
||||
|
||||
if ($data->getAssignedUserId()) {
|
||||
$email->setAssignedUserId($data->getAssignedUserId());
|
||||
$email->addAssignedUserId($data->getAssignedUserId());
|
||||
}
|
||||
|
||||
foreach ($data->getUserIdList() as $uId) {
|
||||
$email->addUserId($uId);
|
||||
}
|
||||
|
||||
$this->setFromStrings($parser, $message, $email);
|
||||
$this->setAddresses($parser, $message, $email);
|
||||
|
||||
foreach ($data->getFolderData() as $uId => $folderId) {
|
||||
$email->setUserColumnFolderId($uId, $folderId);
|
||||
}
|
||||
|
||||
$toSkip = $this->processFilters($email, $data->getFilterList(), true);
|
||||
|
||||
if ($toSkip) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$isSystemEmail = $this->processMessageId($parser, $message, $email);
|
||||
|
||||
if ($isSystemEmail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->processDate($parser, $message, $email);
|
||||
|
||||
$duplicate = $this->findDuplicate($email, $message);
|
||||
|
||||
if ($duplicate && $duplicate->getStatus() !== Email::STATUS_BEING_IMPORTED) {
|
||||
$this->entityManager->refreshEntity($duplicate);
|
||||
|
||||
$this->processDuplicate($duplicate, $data, $email->getGroupFolder()?->getId());
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
$email->setIsAutoReply($this->autoReplyDetector->detect($message));
|
||||
|
||||
$this->processDeliveryDate($parser, $message, $email);
|
||||
|
||||
if (!$email->getDateSent()) {
|
||||
$email->setDateSent(DateTimeField::createNow());
|
||||
}
|
||||
|
||||
$inlineAttachmentList = [];
|
||||
|
||||
if (!$data->fetchOnlyHeader()) {
|
||||
$inlineAttachmentList = $parser->getInlineAttachmentList($message, $email);
|
||||
|
||||
$toSkip = $this->processFilters($email, $data->getFilterList());
|
||||
|
||||
if ($toSkip) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$email->setBody('Not fetched. The email size exceeds the limit.');
|
||||
$email->setIsHtml(false);
|
||||
}
|
||||
|
||||
$this->processInReplyTo($parser, $message, $email);
|
||||
|
||||
$parentFound = $this->parentFinder->find($email, $message);
|
||||
|
||||
if ($parentFound) {
|
||||
$email->setParent($parentFound);
|
||||
}
|
||||
|
||||
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) {
|
||||
$this->entityManager->refreshEntity($duplicate);
|
||||
|
||||
$this->processDuplicate($duplicate, $data, $email->getGroupFolder()?->getId());
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($duplicate) {
|
||||
$this->copyAttributesToDuplicate($email, $duplicate);
|
||||
$this->getEmailRepository()->fillAccount($duplicate);
|
||||
|
||||
$this->processDuplicate($duplicate, $data, $email->getGroupFolder()?->getId());
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
if (!$email->getMessageId()) {
|
||||
$email->setDummyMessageId();
|
||||
}
|
||||
|
||||
$email->setStatus(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->setStatus(Email::STATUS_ARCHIVED);
|
||||
|
||||
$this->processFinalTransactionalSave($email);
|
||||
$this->processAttachmentSave($inlineAttachmentList, $email);
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
private function copyAttributesToDuplicate(Email $email, Email $duplicate): void
|
||||
{
|
||||
$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(Field::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'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function processEmailWithParent(Email $email): void
|
||||
{
|
||||
$parentType = $email->get(Field::PARENT . 'Type');
|
||||
$parentId = $email->get(Field::PARENT . 'Id');
|
||||
|
||||
if (!$parentId || !$parentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$emailKeepParentTeamsEntityList = $this->config->get('emailKeepParentTeamsEntityList') ?? [];
|
||||
|
||||
if (
|
||||
!in_array($parentType, $emailKeepParentTeamsEntityList) ||
|
||||
!$this->entityManager->hasRepository($parentType)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = $email->getParent();
|
||||
|
||||
if (!$parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$parent instanceof CoreEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($parent->getLinkMultipleIdList(Field::TEAMS) as $parentTeamId) {
|
||||
$email->addTeamId($parentTeamId);
|
||||
}
|
||||
}
|
||||
|
||||
private function findDuplicate(Email $email, Message $message): ?Email
|
||||
{
|
||||
return $this->duplicateFinder->find($email, $message);
|
||||
}
|
||||
|
||||
private function processDuplicate(Email $email, Data $data, ?string $groupFolderId): void
|
||||
{
|
||||
$assignedUserId = $data->getAssignedUserId();
|
||||
|
||||
if ($email->getStatus() === Email::STATUS_ARCHIVED) {
|
||||
$this->getEmailRepository()->loadFromField($email);
|
||||
$this->getEmailRepository()->loadToField($email);
|
||||
}
|
||||
|
||||
$fetchedTeamIds = $email->getTeams()->getIdList();
|
||||
$fetchedUserIds = $email->getUsers()->getIdList();
|
||||
$fetchedAssignedUserIds = $email->getAssignedUsers()->getIdList();
|
||||
|
||||
$email->setLinkMultipleIdList('users', []);
|
||||
$email->setLinkMultipleIdList(Field::TEAMS, []);
|
||||
$email->setLinkMultipleIdList(Field::ASSIGNED_USERS, []);
|
||||
|
||||
$processNoteAcl = false;
|
||||
|
||||
if ($assignedUserId) {
|
||||
if (!in_array($assignedUserId, $fetchedUserIds)) {
|
||||
$processNoteAcl = true;
|
||||
|
||||
$email->addUserId($assignedUserId);
|
||||
}
|
||||
|
||||
if (!in_array($assignedUserId, $fetchedAssignedUserIds)) {
|
||||
$email->addAssignedUserId($assignedUserId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data->getUserIdList() as $uId) {
|
||||
if (!in_array($uId, $fetchedUserIds)) {
|
||||
$processNoteAcl = true;
|
||||
|
||||
$email->addUserId($uId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data->getFolderData() as $uId => $folderId) {
|
||||
if (!in_array($uId, $fetchedUserIds)) {
|
||||
$email->setUserColumnFolderId($uId, $folderId);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Can cause skip-notification bypass. @todo Revise.
|
||||
$this->entityManager
|
||||
->getRelation($email, 'users')
|
||||
->updateColumnsById($uId, [Email::USERS_COLUMN_FOLDER_ID => $folderId]);
|
||||
}
|
||||
|
||||
$email->set('isBeingImported', true);
|
||||
|
||||
$this->getEmailRepository()->applyUsersFilters($email);
|
||||
|
||||
if ($groupFolderId && !$email->getGroupFolder()) {
|
||||
$this->relateWithGroupFolder($email, $groupFolderId);
|
||||
|
||||
$addedFromFolder = $this->applyGroupFolder(
|
||||
$email,
|
||||
$groupFolderId,
|
||||
$fetchedUserIds,
|
||||
$fetchedTeamIds
|
||||
);
|
||||
|
||||
if ($addedFromFolder) {
|
||||
$processNoteAcl = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data->getTeamIdList() as $teamId) {
|
||||
if (!in_array($teamId, $fetchedTeamIds)) {
|
||||
$processNoteAcl = true;
|
||||
|
||||
$email->addTeamId($teamId);
|
||||
}
|
||||
}
|
||||
|
||||
$saverParams = SaverParams::create()->withRawOptions([
|
||||
'skipLinkMultipleRemove' => true,
|
||||
'skipLinkMultipleUpdate' => true,
|
||||
]);
|
||||
|
||||
$this->linkMultipleSaver->process($email, 'users', $saverParams);
|
||||
$this->linkMultipleSaver->process($email, Field::ASSIGNED_USERS, $saverParams);
|
||||
$this->linkMultipleSaver->process($email, Field::TEAMS, $saverParams);
|
||||
|
||||
if ($this->notificationsEnabled()) {
|
||||
$notificatorParams = AssignmentNotificatorParams::create()
|
||||
->withRawOptions([Email::SAVE_OPTION_IS_BEING_IMPORTED => true]);
|
||||
|
||||
$this->notificator->process($email, $notificatorParams);
|
||||
}
|
||||
|
||||
$email->set('isBeingImported', false);
|
||||
$email->clear('teamsIds');
|
||||
$email->clear('usersIds');
|
||||
$email->clear('assignedUsersIds');
|
||||
|
||||
$email->setAsFetched();
|
||||
|
||||
if ($email->getParentType() && $processNoteAcl) {
|
||||
$this->scheduleAclJob($email);
|
||||
}
|
||||
}
|
||||
|
||||
private function notificationsEnabled(): bool
|
||||
{
|
||||
return in_array(
|
||||
Email::ENTITY_TYPE,
|
||||
$this->config->get('assignmentNotificationsEntityList') ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private function getSubject(Parser $parser, Message $message): string
|
||||
{
|
||||
$subject = '';
|
||||
|
||||
if ($parser->hasHeader($message, 'subject')) {
|
||||
$subject = $parser->getHeader($message, 'subject');
|
||||
}
|
||||
|
||||
if (!empty($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);
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
private function setFromStrings(Parser $parser, Message $message, Email $email): void
|
||||
{
|
||||
$fromAddressData = $parser->getAddressData($message, 'from');
|
||||
|
||||
if ($fromAddressData) {
|
||||
$namePart = ($fromAddressData->name ? ($fromAddressData->name . ' ') : '');
|
||||
|
||||
$email->set('fromString', "$namePart<$fromAddressData->address>");
|
||||
}
|
||||
|
||||
$replyToData = $parser->getAddressData($message, 'reply-To');
|
||||
|
||||
if ($replyToData) {
|
||||
$namePart = ($replyToData->name ? ($replyToData->name . ' ') : '');
|
||||
|
||||
$email->set('replyToString', "$namePart<$replyToData->address>");
|
||||
}
|
||||
}
|
||||
|
||||
private function setAddresses(Parser $parser, Message $message, Email $email): void
|
||||
{
|
||||
$from = $parser->getAddressList($message, 'from');
|
||||
$to = $parser->getAddressList($message, 'to');
|
||||
$cc = $parser->getAddressList($message, 'cc');
|
||||
$replyTo = $parser->getAddressList($message, 'reply-To');
|
||||
|
||||
$email->setFromAddress($from[0] ?? null);
|
||||
$email->setToAddressList($to);
|
||||
$email->setCcAddressList($cc);
|
||||
$email->setReplyToAddressList($replyTo);
|
||||
|
||||
$email->set('addressNameMap', $parser->getAddressNameMap($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if an email is system.
|
||||
*/
|
||||
private function processMessageId(Parser $parser, Message $message, Email $email): bool
|
||||
{
|
||||
if (!$parser->hasHeader($message, 'message-Id')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$messageId = $parser->getMessageId($message);
|
||||
|
||||
if (!$messageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email->setMessageId($messageId);
|
||||
|
||||
if ($parser->hasHeader($message, 'delivered-To')) {
|
||||
$deliveredTo = $parser->getHeader($message, 'delivered-To') ?? '';
|
||||
|
||||
$email->set('messageIdInternal', "$messageId-$deliveredTo");
|
||||
}
|
||||
|
||||
if (stripos($messageId, '@espo-system') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processDate(Parser $parser, Message $message, Email $email): void
|
||||
{
|
||||
if (!$parser->hasHeader($message, 'date')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dateString = $parser->getHeader($message, 'date') ?? 'now';
|
||||
|
||||
try {
|
||||
$dateSent = DateTimeField::fromDateTime(new DateTime($dateString));
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email->setDateSent($dateSent);
|
||||
}
|
||||
|
||||
private function processDeliveryDate(Parser $parser, Message $message, Email $email): void
|
||||
{
|
||||
if (!$parser->hasHeader($message, 'delivery-Date')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dateString = $parser->getHeader($message, 'delivery-Date') ?? 'now';
|
||||
|
||||
try {
|
||||
$deliveryDate = DateTimeField::fromDateTime(new DateTime($dateString));
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email->setDeliveryDate($deliveryDate);
|
||||
}
|
||||
|
||||
private function processInReplyTo(Parser $parser, Message $message, Email $email): void
|
||||
{
|
||||
if (!$parser->hasHeader($message, 'in-Reply-To')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stringValue = $parser->getHeader($message, 'in-Reply-To');
|
||||
|
||||
if (!$stringValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$values = explode(' ', $stringValue);
|
||||
|
||||
$inReplyTo = $values[0] ?? null;
|
||||
|
||||
if (!$inReplyTo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($inReplyTo[0] !== '<') {
|
||||
$inReplyTo = "<$inReplyTo>";
|
||||
}
|
||||
|
||||
$replied = $this->entityManager
|
||||
->getRDBRepositoryByClass(Email::class)
|
||||
->where(['messageId' => $inReplyTo])
|
||||
->findOne();
|
||||
|
||||
if (!$replied) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email->setReplied($replied);
|
||||
|
||||
foreach ($replied->getTeams()->getIdList() as $teamId) {
|
||||
$email->addTeamId($teamId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<EmailFilter> $filterList
|
||||
* @return bool True if to skip.
|
||||
*/
|
||||
private function processFilters(Email $email, iterable $filterList, bool $skipBody = false): bool
|
||||
{
|
||||
$matchedFilter = $this->filtersMatcher->findMatch($email, $filterList, $skipBody);
|
||||
|
||||
if (!$matchedFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($matchedFilter->getAction() === EmailFilter::ACTION_SKIP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$matchedFilter->getAction() === EmailFilter::ACTION_MOVE_TO_GROUP_FOLDER &&
|
||||
$matchedFilter->getGroupEmailFolderId()
|
||||
) {
|
||||
$this->applyGroupFolder($email, $matchedFilter->getGroupEmailFolderId());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processFinalTransactionalSave(Email $email): void
|
||||
{
|
||||
$this->entityManager->getTransactionManager()->start();
|
||||
|
||||
$this->entityManager
|
||||
->getRDBRepositoryByClass(Email::class)
|
||||
->forUpdate()
|
||||
->where([Attribute::ID => $email->getId()])
|
||||
->findOne();
|
||||
|
||||
$this->entityManager->saveEntity($email, [Email::SAVE_OPTION_IS_BEING_IMPORTED => true]);
|
||||
|
||||
$this->entityManager->getTransactionManager()->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment[] $inlineAttachmentList
|
||||
*/
|
||||
private function processAttachmentSave(array $inlineAttachmentList, Email $email): void
|
||||
{
|
||||
foreach ($inlineAttachmentList as $attachment) {
|
||||
$attachment->setTargetField('body');
|
||||
$attachment->setRelated(LinkParent::createFromEntity($email));
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
private function getParser(Message $message): Parser
|
||||
{
|
||||
return $message instanceof MessageWrapper ?
|
||||
($message->getParser() ?? $this->parserFactory->create()) :
|
||||
$this->parserFactory->create();
|
||||
}
|
||||
|
||||
private function getEmailRepository(): EmailRepository
|
||||
{
|
||||
/** @var EmailRepository */
|
||||
return $this->entityManager->getRDBRepositoryByClass(Email::class);
|
||||
}
|
||||
|
||||
private function relateWithGroupFolder(Email $email, string $groupFolderId): void
|
||||
{
|
||||
$this->entityManager
|
||||
->getRelation($email, 'groupFolder')
|
||||
->relateById($groupFolderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fetchedUserIds
|
||||
* @param string[] $fetchedTeamIds
|
||||
*/
|
||||
private function applyGroupFolder(
|
||||
Email $email,
|
||||
string $groupFolderId,
|
||||
array $fetchedUserIds = [],
|
||||
array $fetchedTeamIds = [],
|
||||
): bool {
|
||||
|
||||
$email->setGroupFolderId($groupFolderId);
|
||||
|
||||
$groupFolder = $this->entityManager
|
||||
->getRDBRepositoryByClass(GroupEmailFolder::class)
|
||||
->getById($groupFolderId);
|
||||
|
||||
if (!$groupFolder || !$groupFolder->getTeams()->getCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$added = false;
|
||||
|
||||
foreach ($groupFolder->getTeams()->getIdList() as $teamId) {
|
||||
if (!in_array($teamId, $fetchedTeamIds)) {
|
||||
$added = true;
|
||||
|
||||
$email->addTeamId($teamId);
|
||||
}
|
||||
}
|
||||
|
||||
$users = $this->entityManager
|
||||
->getRDBRepositoryByClass(User::class)
|
||||
->select([Attribute::ID])
|
||||
->where([
|
||||
'type' => [User::TYPE_REGULAR, User::TYPE_ADMIN],
|
||||
'isActive' => true,
|
||||
Attribute::ID . '!=' => $fetchedUserIds,
|
||||
])
|
||||
->where(
|
||||
Condition::in(
|
||||
Expression::column(Attribute::ID),
|
||||
SelectBuilder::create()
|
||||
->from(Team::RELATIONSHIP_TEAM_USER)
|
||||
->select('userId')
|
||||
->where(['teamId' => $groupFolder->getTeams()->getIdList()])
|
||||
->build()
|
||||
)
|
||||
)
|
||||
->find();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$added = true;
|
||||
|
||||
$email->addUserId($user->getId());
|
||||
}
|
||||
|
||||
return $added;
|
||||
}
|
||||
|
||||
private function scheduleAclJob(Email $email): void
|
||||
{
|
||||
// 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(['notify' => true])
|
||||
->withTargetId($email->getId())
|
||||
->withTargetType(Email::ENTITY_TYPE)
|
||||
)
|
||||
->setQueue(QueueName::Q1)
|
||||
->setTime($dt)
|
||||
->schedule();
|
||||
}
|
||||
}
|
||||
271
application/Espo/Core/Mail/Importer/DefaultParentFinder.php
Normal file
271
application/Espo/Core/Mail/Importer/DefaultParentFinder.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Templates\Entities\Company;
|
||||
use Espo\Core\Templates\Entities\Person;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\EmailAddress;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Repositories\EmailAddress as EmailAddressRepository;
|
||||
|
||||
class DefaultParentFinder implements ParentFinder
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $entityTypeList;
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Config $config,
|
||||
private Metadata $metadata
|
||||
) {
|
||||
$this->entityTypeList = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity(Email::ENTITY_TYPE)
|
||||
->getField(Field::PARENT)
|
||||
->getParam('entityList') ?? [];
|
||||
}
|
||||
|
||||
public function find(Email $email, Message $message): ?Entity
|
||||
{
|
||||
return
|
||||
$this->getByReferences($message) ??
|
||||
$this->getFromReplied($email) ??
|
||||
$this->getByFromAddress($email) ??
|
||||
$this->getByReplyToAddress($email) ??
|
||||
$this->getByToAddress($email);
|
||||
}
|
||||
|
||||
private function isEntityTypeAllowed(string $entityType): bool
|
||||
{
|
||||
return in_array($entityType, $this->entityTypeList);
|
||||
}
|
||||
|
||||
private function getByFromAddress(Email $email): ?Entity
|
||||
{
|
||||
$from = $email->getFromAddress();
|
||||
|
||||
if (!$from) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getByAddress($from);
|
||||
}
|
||||
|
||||
private function getByReplyToAddress(Email $email): ?Entity
|
||||
{
|
||||
$list = $email->getReplyToAddressList();
|
||||
|
||||
if ($list === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getByAddress($list[0]);
|
||||
}
|
||||
|
||||
private function getByToAddress(Email $email): ?Entity
|
||||
{
|
||||
$list = $email->getToAddressList();
|
||||
|
||||
if ($list === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getByAddress($list[0]);
|
||||
}
|
||||
|
||||
private function getByAddress(string $emailAddress): ?Entity
|
||||
{
|
||||
$contact = $this->entityManager
|
||||
->getRDBRepositoryByClass(Contact::class)
|
||||
->where(['emailAddress' => $emailAddress])
|
||||
->findOne();
|
||||
|
||||
if ($contact) {
|
||||
if (
|
||||
!$this->config->get('b2cMode') &&
|
||||
$this->isEntityTypeAllowed(Account::ENTITY_TYPE) &&
|
||||
$contact->getAccount()
|
||||
) {
|
||||
return $contact->getAccount();
|
||||
}
|
||||
|
||||
if ($this->isEntityTypeAllowed(Contact::ENTITY_TYPE)) {
|
||||
return $contact;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isEntityTypeAllowed(Account::ENTITY_TYPE)) {
|
||||
$account = $this->entityManager
|
||||
->getRDBRepositoryByClass(Account::class)
|
||||
->where(['emailAddress' => $emailAddress])
|
||||
->findOne();
|
||||
|
||||
if ($account) {
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isEntityTypeAllowed(Lead::ENTITY_TYPE)) {
|
||||
$lead = $this->entityManager
|
||||
->getRDBRepositoryByClass(Lead::class)
|
||||
->where(['emailAddress' => $emailAddress])
|
||||
->findOne();
|
||||
|
||||
if ($lead) {
|
||||
return $lead;
|
||||
}
|
||||
}
|
||||
|
||||
$entityTypeList = array_filter(
|
||||
$this->entityTypeList,
|
||||
function ($entityType) {
|
||||
return
|
||||
!in_array(
|
||||
$entityType,
|
||||
[Account::ENTITY_TYPE, Contact::ENTITY_TYPE, Lead::ENTITY_TYPE]
|
||||
) &&
|
||||
in_array(
|
||||
$this->metadata->get(['scopes', $entityType, 'type']),
|
||||
[Company::TEMPLATE_TYPE, Person::TEMPLATE_TYPE]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/** @var EmailAddressRepository $emailAddressRepository */
|
||||
$emailAddressRepository = $this->entityManager->getRepository(EmailAddress::ENTITY_TYPE);
|
||||
|
||||
foreach ($entityTypeList as $entityType) {
|
||||
$entity = $emailAddressRepository->getEntityByAddress($emailAddress, $entityType);
|
||||
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getFromReplied(Email $email): ?Entity
|
||||
{
|
||||
$replied = $email->getReplied();
|
||||
|
||||
return $replied?->getParent();
|
||||
}
|
||||
|
||||
private function getByReferences(Message $message): ?Entity
|
||||
{
|
||||
$references = $message->getHeader('References');
|
||||
|
||||
if (!$references) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$delimiter = strpos($references, '>,') ? ',' : ' ';
|
||||
|
||||
foreach (explode($delimiter, $references) as $reference) {
|
||||
$reference = str_replace(['/', '@'], ' ', trim(trim($reference), '<>'));
|
||||
|
||||
$parent = $this->getByReferencesItem($reference);
|
||||
|
||||
if ($parent) {
|
||||
return $parent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getByReferencesItem(string $reference): ?Entity
|
||||
{
|
||||
$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 null;
|
||||
}
|
||||
|
||||
if (!$parentType || !$parentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($parentType) || !is_string($parentId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->entityManager->hasRepository($parentType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntityById($parentType, $parentId);
|
||||
|
||||
if ($parent instanceof Lead) {
|
||||
return $this->getFromLead($parent) ?? $parent;
|
||||
}
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
private function getFromLead(Lead $lead): ?Entity
|
||||
{
|
||||
if ($lead->getStatus() !== Lead::STATUS_CONVERTED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($lead->getCreatedAccount()) {
|
||||
return $lead->getCreatedAccount();
|
||||
}
|
||||
|
||||
if (
|
||||
$this->config->get('b2cMode') &&
|
||||
$lead->getCreatedContact()
|
||||
) {
|
||||
return $lead->getCreatedContact();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Mail/Importer/DuplicateFinder.php
Normal file
41
application/Espo/Core/Mail/Importer/DuplicateFinder.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Entities\Email;
|
||||
|
||||
/**
|
||||
* Finds an existing duplicate of an email being imported.
|
||||
*/
|
||||
interface DuplicateFinder
|
||||
{
|
||||
public function find(Email $email, Message $message): ?Email;
|
||||
}
|
||||
42
application/Espo/Core/Mail/Importer/ParentFinder.php
Normal file
42
application/Espo/Core/Mail/Importer/ParentFinder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Mail\Importer;
|
||||
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* Finds a parent record for an email being imported.
|
||||
*/
|
||||
interface ParentFinder
|
||||
{
|
||||
public function find(Email $email, Message $message): ?Entity;
|
||||
}
|
||||
Reference in New Issue
Block a user