From 9cb8d9d65e2b86d2653ef189dc2eec574adcad05 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sun, 4 Apr 2021 19:55:08 +0300 Subject: [PATCH] refactoring --- .../Espo/Core/Acl/OwnerUserFieldProvider.php | 6 +- application/Espo/Core/Mail/Importer.php | 213 ++++++++++++------ .../Espo/Hooks/Common/StreamNotesAcl.php | 17 +- application/Espo/Services/Note.php | 185 --------------- application/Espo/Services/Stream.php | 197 ++++++++++++++++ tests/integration/Espo/Note/AclTest.php | 4 - 6 files changed, 361 insertions(+), 261 deletions(-) diff --git a/application/Espo/Core/Acl/OwnerUserFieldProvider.php b/application/Espo/Core/Acl/OwnerUserFieldProvider.php index 35cbe591a3..767ee98224 100644 --- a/application/Espo/Core/Acl/OwnerUserFieldProvider.php +++ b/application/Espo/Core/Acl/OwnerUserFieldProvider.php @@ -51,13 +51,17 @@ class OwnerUserFieldProvider $this->metadata = $metadata; } + /** + * Get an entity field that stores an owner-user (or multiple users). + * Must be link or linkMulitple field. NULL means no owner. + */ public function get(string $entityType) : ?string { $value = $this->metadata->get(['entityAcl', $entityType, 'readOwnerUserField']); if ($value) { return $value; - } + } $defs = $this->ormDefs->getEntity($entityType); diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index d78c7b4197..3126cf9518 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -29,8 +29,6 @@ namespace Espo\Core\Mail; -use Laminas\Mime\Mime as Mime; - use Espo\Entities\Email; use Espo\Core\{ @@ -41,8 +39,6 @@ use Espo\Core\{ Mail\Parsers\MailMimeParser, }; -use Espo\Notificators\EmailNotificator; - use DateTime; use DateTimeZone; use Exception; @@ -65,7 +61,10 @@ class Importer protected $parserClassName; public function __construct( - EntityManager $entityManager, Config $config, ?Notificator $notificator = null, ?string $parserClassName = null + EntityManager $entityManager, + Config $config, + ?Notificator $notificator = null, + ?string $parserClassName = null ) { $this->entityManager = $entityManager; $this->config = $config; @@ -85,6 +84,7 @@ class Importer bool $fetchOnlyHeader = false, ?array $folderData = null ) : ?Email { + $parser = $message->getParser(); $parserClassName = $this->parserClassName; @@ -134,13 +134,16 @@ class Importer if ($fromAddressData) { $fromString = ($fromAddressData['name'] ? ($fromAddressData['name'] . ' ') : '') . '<' . $fromAddressData['address'] .'>'; + $email->set('fromString', $fromString); } $replyToData = $parser->getAddressDataFromMessage($message, 'reply-To'); if ($replyToData) { - $replyToString = ($replyToData['name'] ? ($replyToData['name'] . ' ') : '') . '<' . $replyToData['address'] .'>'; + $replyToString = ($replyToData['name'] ? ($replyToData['name'] . ' ') : '') . + '<' . $replyToData['address'] .'>'; + $email->set('replyToString', $replyToString); } @@ -170,13 +173,19 @@ class Importer return null; } - if ($parser->checkMessageAttribute($message, 'message-Id') && $parser->getMessageAttribute($message, 'message-Id')) { + if ( + $parser->checkMessageAttribute($message, 'message-Id') && + $parser->getMessageAttribute($message, 'message-Id') + ) { $messageId = $parser->getMessageMessageId($message); $email->set('messageId', $messageId); if ($parser->checkMessageAttribute($message, 'delivered-To')) { - $email->set('messageIdInternal', $messageId . '-' . $parser->getMessageAttribute($message, 'delivered-To')); + $email->set( + 'messageIdInternal', + $messageId . '-' . $parser->getMessageAttribute($message, 'delivered-To') + ); } if (stripos($messageId, '@espo-system') !== false) { @@ -184,9 +193,9 @@ class Importer } } - $duplicate = null; + $duplicate = $this->findDuplicate($email); - if ($duplicate = $this->findDuplicate($email)) { + if ($duplicate) { if ($duplicate->get('status') != 'Being Imported') { $duplicate = $this->entityManager->getEntity('Email', $duplicate->id); @@ -202,10 +211,13 @@ class Importer if ($dt) { $dateSent = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $email->set('dateSent', $dateSent); } - } catch (Exception $e) {} - } else { + } + catch (Exception $e) {} + } + else { $email->set('dateSent', date('Y-m-d H:i:s')); } @@ -215,9 +227,11 @@ class Importer if ($dt) { $deliveryDate = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $email->set('delivery-Date', $deliveryDate); } - } catch (Exception $e) {} + } + catch (Exception $e) {} } $inlineAttachmentList = []; @@ -228,7 +242,8 @@ class Importer if ($this->filtersMatcher->match($email, $filterList)) { return null; } - } else { + } + else { $email->set('body', 'Not fetched. The email size exceeds the limit.'); $email->set('isHtml', false); } @@ -238,9 +253,11 @@ class Importer $replied = null; if ( - $parser->checkMessageAttribute($message, 'in-Reply-To') && $parser->getMessageAttribute($message, 'in-Reply-To') + $parser->checkMessageAttribute($message, 'in-Reply-To') && + $parser->getMessageAttribute($message, 'in-Reply-To') ) { $arr = explode(' ', $parser->getMessageAttribute($message, 'in-Reply-To')); + $inReplyTo = $arr[0]; if ($inReplyTo) { @@ -258,6 +275,7 @@ class Importer if ($replied) { $email->set('repliedId', $replied->id); $repliedTeamIdList = $replied->getLinkMultipleIdList('teams'); + foreach ($repliedTeamIdList as $repliedTeamId) { $email->addLinkMultipleId('teams', $repliedTeamId); } @@ -265,7 +283,10 @@ class Importer } } - if ($parser->checkMessageAttribute($message, 'references') && $parser->getMessageAttribute($message, 'references')) { + if ( + $parser->checkMessageAttribute($message, 'references') && + $parser->getMessageAttribute($message, 'references') + ) { $references = $parser->getMessageAttribute($message, 'references'); $delimiter = ' '; @@ -278,6 +299,7 @@ class Importer foreach ($arr as $reference) { $reference = trim($reference); $reference = str_replace(['/', '@'], " ", trim($reference, '<>')); + $parentType = $parentId = null; $emailSent = PHP_INT_MAX; $number = null; @@ -295,15 +317,20 @@ class Importer if ($parent && $parent->get('status') == 'Converted') { if ($parent->get('createdAccountId')) { - $account = $this->entityManager->getEntity('Account', $parent->get('createdAccountId')); + $account = $this->entityManager + ->getEntity('Account', $parent->get('createdAccountId')); + if ($account) { $parentType = 'Account'; $parentId = $account->id; } - } else { + } + else { if ($this->config->get('b2cMode')) { if ($parent->get('createdContactId')) { - $contact = $this->entityManager->getEntity('Contact', $parent->get('createdContactId')); + $contact = $this->entityManager + ->getEntity('Contact', $parent->get('createdContactId')); + if ($contact) { $parentType = 'Contact'; $parentId = $contact->id; @@ -325,7 +352,8 @@ class Importer if (!$parentFound) { if ($replied && $replied->get('parentId') && $replied->get('parentType')) { - $parentFound = $this->entityManager->getEntity($replied->get('parentType'), $replied->get('parentId')); + $parentFound = $this->entityManager + ->getEntity($replied->get('parentType'), $replied->get('parentId')); if ($parentFound) { $email->set('parentType', $replied->get('parentType')); @@ -340,11 +368,13 @@ class Importer $parentFound = $this->findParent($email, $from); } } + if (!$parentFound) { if (!empty($replyToArr)) { $parentFound = $this->findParent($email, $replyToArr[0]); } } + if (!$parentFound) { if (!empty($toArr)) { $parentFound = $this->findParent($email, $toArr[0]); @@ -359,6 +389,7 @@ class Importer if ($duplicate->get('status') != 'Being Imported') { $duplicate = $this->entityManager->getEntity('Email', $duplicate->id); + $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList); return $duplicate; @@ -408,12 +439,17 @@ class Importer 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); } @@ -434,61 +470,75 @@ class Importer 'relatedType' => 'Email', 'field' => 'body', ]); + $this->entityManager->saveEntity($attachment); } return $email; } - protected function findParent(Email $email, $emailAddress) + protected function findParent(Email $email, $emailAddress): bool { - $contact = $this->entityManager->getRepository('Contact')->where([ - 'emailAddress' => $emailAddress - ])->findOne(); + $contact = $this->entityManager + ->getRepository('Contact') + ->where([ + 'emailAddress' => $emailAddress + ]) + ->findOne(); if ($contact) { - if (!$this->config->get('b2cMode')) { - if ($contact->get('accountId')) { - $email->set('parentType', 'Account'); - $email->set('parentId', $contact->get('accountId')); + if (!$this->config->get('b2cMode') && $contact->get('accountId')) { + $email->set('parentType', 'Account'); + $email->set('parentId', $contact->get('accountId')); - return true; - } + return true; } + $email->set('parentType', 'Contact'); $email->set('parentId', $contact->id); return true; } - else { - $account = $this->entityManager->getRepository('Account')->where([ - 'emailAddress' => $emailAddress - ])->findOne(); - if ($account) { - $email->set('parentType', 'Account'); - $email->set('parentId', $account->id); - return true; - } else { - $lead = $this->entityManager->getRepository('Lead')->where([ - 'emailAddress' => $emailAddress - ])->findOne(); - if ($lead) { - $email->set('parentType', 'Lead'); - $email->set('parentId', $lead->id); - return true; - } - } + $account = $this->entityManager + ->getRepository('Account') + ->where([ + 'emailAddress' => $emailAddress + ]) + ->findOne(); + + if ($account) { + $email->set('parentType', 'Account'); + $email->set('parentId', $account->id); + + return true; } + + $lead = $this->entityManager + ->getRepository('Lead') + ->where([ + 'emailAddress' => $emailAddress + ]) + ->findOne(); + + if ($lead) { + $email->set('parentType', 'Lead'); + $email->set('parentId', $lead->id); + + return true; + } + + return false; } - protected function findDuplicate(Email $email) : ?Email + protected function findDuplicate(Email $email): ?Email { if (!$email->get('messageId')) { return null; } - $duplicate = $this->entityManager->getRepository('Email') + $duplicate = $this->entityManager + ->getRepository('Email') ->select(['id', 'status']) ->where([ 'messageId' => $email->get('messageId'), @@ -498,15 +548,23 @@ class Importer return $duplicate; } - protected function processDuplicate(Email $duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList) - { + protected function processDuplicate( + Email $duplicate, + $assignedUserId, + $userIdList, + $folderData, + $teamsIdList + ): void { + if ($duplicate->get('status') == 'Archived') { $this->entityManager->getRepository('Email')->loadFromField($duplicate); $this->entityManager->getRepository('Email')->loadToField($duplicate); } $duplicate->loadLinkMultipleField('users'); + $fetchedUserIdList = $duplicate->getLinkMultipleIdList('users'); + $duplicate->setLinkMultipleIdList('users', []); $processNoteAcl = false; @@ -514,8 +572,10 @@ class Importer if ($assignedUserId) { if (!in_array($assignedUserId, $fetchedUserIdList)) { $processNoteAcl = true; + $duplicate->addLinkMultipleId('users', $assignedUserId); } + $duplicate->addLinkMultipleId('assignedUsers', $assignedUserId); } @@ -523,6 +583,7 @@ class Importer foreach ($userIdList as $uId) { if (!in_array($uId, $fetchedUserIdList)) { $processNoteAcl = true; + $duplicate->addLinkMultipleId('users', $uId); } } @@ -532,29 +593,41 @@ class Importer foreach ($folderData as $uId => $folderId) { if (!in_array($uId, $fetchedUserIdList)) { $duplicate->setLinkMultipleColumn('users', 'folderId', $uId, $folderId); - } else { - $this->entityManager->getRepository('Email')->updateRelation($duplicate, 'users', $uId, [ + + continue; + } + + $this->entityManager + ->getRepository('Email') + ->updateRelation($duplicate, 'users', $uId, [ 'folderId' => $folderId, ]); - } } } $duplicate->set('isBeingImported', true); - $this->entityManager->getRepository('Email')->applyUsersFilters($duplicate); + $this->entityManager + ->getRepository('Email') + ->applyUsersFilters($duplicate); - $this->entityManager->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'users', [ - 'skipLinkMultipleRemove' => true, - 'skipLinkMultipleUpdate' => true, - ]); + $this->entityManager + ->getRepository('Email') + ->processLinkMultipleFieldSave($duplicate, 'users', [ + 'skipLinkMultipleRemove' => true, + 'skipLinkMultipleUpdate' => true, + ]); - $this->entityManager->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'assignedUsers', [ - 'skipLinkMultipleRemove' => true, - 'skipLinkMultipleUpdate' => true, - ]); + $this->entityManager + ->getRepository('Email') + ->processLinkMultipleFieldSave($duplicate, 'assignedUsers', [ + 'skipLinkMultipleRemove' => true, + 'skipLinkMultipleUpdate' => true, + ]); - if ($notificator = $this->notificator) { + $notificator = $this->notificator; + + if ($notificator) { $notificator->process($duplicate, [ 'isBeingImported' => true, ]); @@ -566,20 +639,27 @@ class Importer foreach ($teamsIdList as $teamId) { if (!in_array($teamId, $fetchedTeamIdList)) { $processNoteAcl = true; + $this->entityManager->getRepository('Email')->relate($duplicate, 'teams', $teamId); } } } if ($duplicate->get('parentType') && $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('+5 seconds'); $executeAt = $dt->format('Y-m-d H:i:s'); $job = $this->entityManager->getEntity('Job'); + $job->set([ - 'serviceName' => 'Note', + 'serviceName' => 'Stream', 'methodName' => 'processNoteAclJob', 'data' => [ 'targetType' => 'Email', @@ -588,6 +668,7 @@ class Importer 'executeAt' => $executeAt, 'queue' => 'q1', ]); + $this->entityManager->saveEntity($job); } } diff --git a/application/Espo/Hooks/Common/StreamNotesAcl.php b/application/Espo/Hooks/Common/StreamNotesAcl.php index aefc67dafa..dc31a5ede7 100644 --- a/application/Espo/Hooks/Common/StreamNotesAcl.php +++ b/application/Espo/Hooks/Common/StreamNotesAcl.php @@ -35,13 +35,20 @@ use Espo\Core\{ ServiceFactory, }; +/** + * Notes having `related` or `superParent` are subjects to access control + * through `users` and `teams` fields. + * + * When users or teams of `related` or `parent` record are changed + * the note record will be changed too. + */ class StreamNotesAcl { - protected $noteService = null; + private $streamService = null; public static $order = 10; - protected $serviceFactory; + private $serviceFactory; public function __construct(ServiceFactory $serviceFactory) { @@ -66,12 +73,12 @@ class StreamNotesAcl return; } - if (!$this->noteService) { - $this->noteService = $this->serviceFactory->create('Note'); + if (!$this->streamService) { + $this->streamService = $this->serviceFactory->create('Stream'); } $forceProcessNoteNotifications = !empty($options['forceProcessNoteNotifications']); - $this->noteService->processNoteAcl($entity, $forceProcessNoteNotifications); + $this->streamService->processNoteAcl($entity, $forceProcessNoteNotifications); } } diff --git a/application/Espo/Services/Note.php b/application/Espo/Services/Note.php index 12f69c8c08..325eab073b 100644 --- a/application/Espo/Services/Note.php +++ b/application/Espo/Services/Note.php @@ -40,14 +40,10 @@ use Espo\Entities\Note as NoteEntity; use Espo\ORM\Entity; -use DateTime; use StdClass; -use Exception; class Note extends Record { - protected $noteNotificationPeriod = '1 hour'; - public function loadAdditionalFields(Entity $entity) { parent::loadAdditionalFields($entity); @@ -357,185 +353,4 @@ class Note extends Record parent::unlink($id, $link, $foreignId); } - - public function processNoteAclJob(StdClass $data): void - { - $targetType = $data->targetType; - $targetId = $data->targetId; - - if ($targetType && $targetId && $this->entityManager->hasRepository($targetType)) { - $entity = $this->entityManager->getEntity($targetType, $targetId); - - if ($entity) { - $this->processNoteAcl($entity, true); - } - } - } - - /** - * Changes users and teams of notes related to an entity according users and teams of the entity. - */ - public function processNoteAcl(Entity $entity, bool $forceProcessNoteNotifications = false): void - { - $entityType = $entity->getEntityType(); - - if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) { - return; - } - - if (!$this->getMetadata()->get(['scopes', $entityType, 'acl'])) { - return; - } - - if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) { - return; - } - - $usersAttributeIsChanged = false; - $teamsAttributeIsChanged = false; - - $ownerUserField = $this->getAclManager()->getReadOwnerUserField($entityType); - - /* @var $defs \Espo\ORM\Defs\EntityDefs */ - $defs = $this->entityManager->getDefs()->getEntity($entity->getEntityType()); - - $userIdList = []; - - if ($ownerUserField) { - if (!$defs->hasField($ownerUserField)) { - throw new Error("Non-existing read-owner user field."); - } - - $fieldDefs = $defs->getField($ownerUserField); - - if ($fieldDefs->getType() === 'linkMultiple') { - $ownerUserIdAttribute = $ownerUserField . 'Ids'; - } - else if ($fieldDefs->getType() === 'link') { - $ownerUserIdAttribute = $ownerUserField . 'Id'; - } - else { - throw new Error("Bad read-owner user field type."); - } - - if ($entity->isAttributeChanged($ownerUserIdAttribute)) { - $usersAttributeIsChanged = true; - } - - if ($usersAttributeIsChanged || $forceProcessNoteNotifications) { - if ($fieldDefs->getType() === 'linkMultiple') { - $userIdList = $entity->getLinkMultipleIdList($ownerUserField); - } - else { - $userId = $entity->get($ownerUserIdAttribute); - - $userIdList = $userId ? [$userId] : []; - } - } - } - - if ($entity->hasLinkMultipleField('teams')) { - if ($entity->isAttributeChanged('teamsIds')) { - $teamsAttributeIsChanged = true; - } - - if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) { - $teamIdList = $entity->getLinkMultipleIdList('teams'); - } - } - - if (!$usersAttributeIsChanged && !$teamsAttributeIsChanged && !$forceProcessNoteNotifications) { - return; - } - - $noteList = $this->entityManager - ->getRepository('Note') - ->where([ - 'OR' => [ - [ - 'relatedId' => $entity->getId(), - 'relatedType' => $entityType, - ], - [ - 'parentId' => $entity->getId(), - 'parentType' => $entityType, - 'superParentId!=' => null, - 'relatedId' => null, - ] - ] - ]) - ->select([ - 'id', - 'parentType', - 'parentId', - 'superParentType', - 'superParentId', - 'isInternal', - 'relatedType', - 'relatedId', - 'createdAt', - ]) - ->find(); - - $noteOptions = []; - - if (!empty($forceProcessNoteNotifications)) { - $noteOptions['forceProcessNotifications'] = true; - } - - $period = '-' . $this->getConfig()->get('noteNotificationPeriod', $this->noteNotificationPeriod); - - $threshold = (new DateTime())->modify($period); - - - foreach ($noteList as $note) { - $this->processNoteAclItem($entity, $note, [ - 'teamsAttributeIsChanged' => $teamsAttributeIsChanged, - 'usersAttributeIsChanged' => $usersAttributeIsChanged, - 'forceProcessNoteNotifications' => $forceProcessNoteNotifications, - 'teamIdList' => $teamIdList, - 'userIdList' => $userIdList, - 'threshold' => $threshold, - ]); - } - } - - protected function processNoteAclItem(Entity $entity, NoteEntity $note, array $params): void - { - $teamsAttributeIsChanged = $params['teamsAttributeIsChanged']; - $usersAttributeIsChanged = $params['usersAttributeIsChanged']; - $forceProcessNoteNotifications = $params['forceProcessNoteNotifications']; - - $teamIdList = $params['teamIdList']; - $userIdList = $params['userIdList']; - - $threshold = $params['threshold']; - - $noteOptions = [ - 'forceProcessNotifications' => $forceProcessNoteNotifications, - ]; - - if (!$entity->isNew() && $note->get('createdAt')) { - try { - $createdAtDt = new DateTime($note->get('createdAt')); - } - catch (Exception $e) { - return; - } - - if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) { - return; - } - } - - if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) { - $note->set('teamsIds', $teamIdList); - } - - if ($usersAttributeIsChanged || $forceProcessNoteNotifications) { - $note->set('usersIds', $userIdList); - } - - $this->entityManager->saveEntity($note, $noteOptions); - } } diff --git a/application/Espo/Services/Stream.php b/application/Espo/Services/Stream.php index b18742cac8..629a6b0f37 100644 --- a/application/Espo/Services/Stream.php +++ b/application/Espo/Services/Stream.php @@ -40,6 +40,7 @@ use Espo\ORM\{ use Espo\{ Entities\User, + Entities\Note as NoteEntity, Services\Notification as NotificationService, }; @@ -58,6 +59,7 @@ use Espo\Core\{ }; use StdClass; +use DateTime; class Stream { @@ -93,6 +95,8 @@ class Stream protected $selectBuilderFactory; + protected const NOTE_ACL_PERIOD = '1 hour'; + public function __construct( EntityManager $entityManager, Config $config, @@ -1210,6 +1214,13 @@ class Stream } } + /** + * Notes having `related` or `superParent` are subjects to access control + * through `users` and `teams` fields. + * + * When users or teams of `related` or `parent` record are changed + * the note record will be changed too. + */ protected function processNoteTeamsUsers(Entity $note, Entity $entity): void { $note->setAclIsProcessed(); @@ -2078,4 +2089,190 @@ class Stream return $userList; } + + public function processNoteAclJob(StdClass $data): void + { + $targetType = $data->targetType; + $targetId = $data->targetId; + + if ($targetType && $targetId && $this->entityManager->hasRepository($targetType)) { + $entity = $this->entityManager->getEntity($targetType, $targetId); + + if ($entity) { + $this->processNoteAcl($entity, true); + } + } + } + + /** + * Changes users and teams of notes related to an entity according users and teams of the entity. + * + * Notes having `related` or `superParent` are subjects to access control + * through `users` and `teams` fields. + * + * When users or teams of `related` or `parent` record are changed + * the note record will be changed too. + */ + public function processNoteAcl(Entity $entity, bool $forceProcessNoteNotifications = false): void + { + $entityType = $entity->getEntityType(); + + if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) { + return; + } + + if (!$this->metadata->get(['scopes', $entityType, 'acl'])) { + return; + } + + if (!$this->metadata->get(['scopes', $entityType, 'object'])) { + return; + } + + $usersAttributeIsChanged = false; + $teamsAttributeIsChanged = false; + + $ownerUserField = $this->aclManager->getReadOwnerUserField($entityType); + + /* @var $defs \Espo\ORM\Defs\EntityDefs */ + $defs = $this->entityManager->getDefs()->getEntity($entity->getEntityType()); + + $userIdList = []; + + if ($ownerUserField) { + if (!$defs->hasField($ownerUserField)) { + throw new Error("Non-existing read-owner user field."); + } + + $fieldDefs = $defs->getField($ownerUserField); + + if ($fieldDefs->getType() === 'linkMultiple') { + $ownerUserIdAttribute = $ownerUserField . 'Ids'; + } + else if ($fieldDefs->getType() === 'link') { + $ownerUserIdAttribute = $ownerUserField . 'Id'; + } + else { + throw new Error("Bad read-owner user field type."); + } + + if ($entity->isAttributeChanged($ownerUserIdAttribute)) { + $usersAttributeIsChanged = true; + } + + if ($usersAttributeIsChanged || $forceProcessNoteNotifications) { + if ($fieldDefs->getType() === 'linkMultiple') { + $userIdList = $entity->getLinkMultipleIdList($ownerUserField); + } + else { + $userId = $entity->get($ownerUserIdAttribute); + + $userIdList = $userId ? [$userId] : []; + } + } + } + + if ($entity->hasLinkMultipleField('teams')) { + if ($entity->isAttributeChanged('teamsIds')) { + $teamsAttributeIsChanged = true; + } + + if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) { + $teamIdList = $entity->getLinkMultipleIdList('teams'); + } + } + + if (!$usersAttributeIsChanged && !$teamsAttributeIsChanged && !$forceProcessNoteNotifications) { + return; + } + + $noteList = $this->entityManager + ->getRepository('Note') + ->where([ + 'OR' => [ + [ + 'relatedId' => $entity->getId(), + 'relatedType' => $entityType, + ], + [ + 'parentId' => $entity->getId(), + 'parentType' => $entityType, + 'superParentId!=' => null, + 'relatedId' => null, + ] + ] + ]) + ->select([ + 'id', + 'parentType', + 'parentId', + 'superParentType', + 'superParentId', + 'isInternal', + 'relatedType', + 'relatedId', + 'createdAt', + ]) + ->find(); + + $noteOptions = []; + + if (!empty($forceProcessNoteNotifications)) { + $noteOptions['forceProcessNotifications'] = true; + } + + $period = '-' . $this->config->get('noteNotificationPeriod', self::NOTE_ACL_PERIOD); + + $threshold = (new DateTime())->modify($period); + + foreach ($noteList as $note) { + $this->processNoteAclItem($entity, $note, [ + 'teamsAttributeIsChanged' => $teamsAttributeIsChanged, + 'usersAttributeIsChanged' => $usersAttributeIsChanged, + 'forceProcessNoteNotifications' => $forceProcessNoteNotifications, + 'teamIdList' => $teamIdList, + 'userIdList' => $userIdList, + 'threshold' => $threshold, + ]); + } + } + + protected function processNoteAclItem(Entity $entity, NoteEntity $note, array $params): void + { + $teamsAttributeIsChanged = $params['teamsAttributeIsChanged']; + $usersAttributeIsChanged = $params['usersAttributeIsChanged']; + $forceProcessNoteNotifications = $params['forceProcessNoteNotifications']; + + $teamIdList = $params['teamIdList']; + $userIdList = $params['userIdList']; + + $threshold = $params['threshold']; + + $noteOptions = [ + 'forceProcessNotifications' => $forceProcessNoteNotifications, + ]; + + if (!$entity->isNew() && $note->get('createdAt')) { + try { + $createdAtDt = new DateTime($note->get('createdAt')); + } + catch (Exception $e) { + return; + } + + if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) { + return; + } + } + + if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) { + $note->set('teamsIds', $teamIdList); + } + + if ($usersAttributeIsChanged || $forceProcessNoteNotifications) { + $note->set('usersIds', $userIdList); + } + + $this->entityManager->saveEntity($note, $noteOptions); + } } diff --git a/tests/integration/Espo/Note/AclTest.php b/tests/integration/Espo/Note/AclTest.php index cab493be7e..7a46038549 100644 --- a/tests/integration/Espo/Note/AclTest.php +++ b/tests/integration/Espo/Note/AclTest.php @@ -31,7 +31,6 @@ namespace tests\integration\Espo\Note; use Espo\{ Services\Stream as StreamService, - Services\Note as NoteService, ORM\EntityManager, }; @@ -42,9 +41,6 @@ class AclTest extends \tests\integration\Core\BaseTestCase /* @var $em EntityManager*/ $em = $this->getContainer()->get('entityManager'); - /* @var $noteService NoteService*/ - $noteService = $this->getContainer()->get('serviceFactory')->create('Note'); - /* @var $streamService StreamService*/ $streamService = $this->getContainer()->get('serviceFactory')->create('Stream');