From b999273b6d52a74a2489c8d28b07ef1d4054c5c2 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 20 Nov 2024 11:09:10 +0200 Subject: [PATCH] unfollow portal users --- .../Modules/Crm/Hooks/CaseObj/Contacts.php | 62 ++++++++--------- .../Modules/Crm/Hooks/CaseObj/IsInternal.php | 68 +++++++++++++++++++ application/Espo/Tools/Stream/Service.php | 36 ++++++++++ 3 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 application/Espo/Modules/Crm/Hooks/CaseObj/IsInternal.php diff --git a/application/Espo/Modules/Crm/Hooks/CaseObj/Contacts.php b/application/Espo/Modules/Crm/Hooks/CaseObj/Contacts.php index 39a8fb27b4..9452e47eae 100644 --- a/application/Espo/Modules/Crm/Hooks/CaseObj/Contacts.php +++ b/application/Espo/Modules/Crm/Hooks/CaseObj/Contacts.php @@ -34,7 +34,6 @@ use Espo\Core\Hook\Hook\AfterSave; use Espo\Core\InjectableFactory; use Espo\Entities\User; use Espo\Modules\Crm\Entities\CaseObj; -use Espo\Modules\Crm\Entities\Contact; use Espo\ORM\Entity; use Espo\ORM\EntityManager; use Espo\ORM\Name\Attribute; @@ -48,6 +47,9 @@ class Contacts implements AfterSave { private ?StreamService $streamService = null; + private const ATTR_CONTACT_ID = 'contactId'; + private const RELATION_CONTACTS = 'contacts'; + public function __construct( private EntityManager $entityManager, private InjectableFactory $injectableFactory, @@ -59,23 +61,20 @@ class Contacts implements AfterSave */ public function afterSave(Entity $entity, SaveOptions $options): void { - if (!$entity->isAttributeChanged('contactId')) { + if (!$entity->isAttributeChanged(self::ATTR_CONTACT_ID)) { return; } - /** @var ?string $contactId */ - $contactId = $entity->get('contactId'); - $contactIdList = $entity->get('contactsIds') ?? []; - /** @var ?string $fetchedContactId */ - $fetchedContactId = $entity->getFetched('contactId'); + $contact = $entity->getContact(); - $relation = $this->entityManager - ->getRDBRepositoryByClass(CaseObj::class) - ->getRelation($entity, 'contacts'); + /** @var ?string $fetchedContactId */ + $fetchedContactId = $entity->getFetched(self::ATTR_CONTACT_ID); + + $contactsRelation = $this->entityManager->getRelation($entity, self::RELATION_CONTACTS); if ($fetchedContactId) { $previousPortalUser = $this->entityManager - ->getRDBRepository(User::ENTITY_TYPE) + ->getRDBRepositoryByClass(User::class) ->select([Attribute::ID]) ->where([ 'contactId' => $fetchedContactId, @@ -90,25 +89,17 @@ class Contacts implements AfterSave } } - if (!$contactId && $fetchedContactId) { - $relation->unrelateById($fetchedContactId); + if (!$contact && $fetchedContactId) { + $contactsRelation->unrelateById($fetchedContactId); return; } - if (!$contactId) { + if (!$contact) { return; } - $portalUser = $this->entityManager - ->getRDBRepository(User::ENTITY_TYPE) - ->select([Attribute::ID]) - ->where([ - 'contactId' => $contactId, - 'type' => User::TYPE_PORTAL, - 'isActive' => true, - ]) - ->findOne(); + $portalUser = $this->getPortalUser($contact->getId()); if ($portalUser) { // @todo Solve ACL check issue when a user is in multiple portals. @@ -119,21 +110,15 @@ class Contacts implements AfterSave } } - if (in_array($contactId, $contactIdList)) { + if (in_array($contact->getId(), $entity->getContacts()->getIdList())) { return; } - $contact = $this->entityManager->getEntityById(Contact::ENTITY_TYPE, $contactId); - - if (!$contact) { + if ($contactsRelation->isRelatedById($contact->getId())) { return; } - if ($relation->isRelated($contact)) { - return; - } - - $relation->relateById($contactId); + $contactsRelation->relateById($contact->getId()); } private function getStreamService(): StreamService @@ -144,4 +129,17 @@ class Contacts implements AfterSave return $this->streamService; } + + private function getPortalUser(?string $contactId): ?User + { + return $this->entityManager + ->getRDBRepositoryByClass(User::class) + ->select([Attribute::ID]) + ->where([ + 'contactId' => $contactId, + 'type' => User::TYPE_PORTAL, + 'isActive' => true, + ]) + ->findOne(); + } } diff --git a/application/Espo/Modules/Crm/Hooks/CaseObj/IsInternal.php b/application/Espo/Modules/Crm/Hooks/CaseObj/IsInternal.php new file mode 100644 index 0000000000..ffd198f4d1 --- /dev/null +++ b/application/Espo/Modules/Crm/Hooks/CaseObj/IsInternal.php @@ -0,0 +1,68 @@ +. + * + * 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\Modules\Crm\Hooks\CaseObj; + +use Espo\Core\FieldProcessing\Stream\FollowersLoader; +use Espo\Core\Hook\Hook\AfterSave; +use Espo\Modules\Crm\Entities\CaseObj; +use Espo\ORM\Entity; +use Espo\ORM\Repository\Option\SaveOptions; +use Espo\Services\Stream; + + +/** + * @implements AfterSave + */ +class IsInternal implements AfterSave +{ + public function __construct( + private Stream $streamService, + private FollowersLoader $followersLoader, + ) {} + + /** + * @param CaseObj $entity + */ + public function afterSave(Entity $entity, SaveOptions $options): void + { + $this->processUnfollowPortalUsers($entity); + } + + private function processUnfollowPortalUsers(CaseObj $entity): void + { + if (!$entity->isInternal() || $entity->isNew()) { + return; + } + + $this->streamService->unfollowPortalUsersFromEntity($entity); + + $this->followersLoader->processFollowers($entity); + } +} diff --git a/application/Espo/Tools/Stream/Service.php b/application/Espo/Tools/Stream/Service.php index a1a09b2ce2..23e85d927b 100644 --- a/application/Espo/Tools/Stream/Service.php +++ b/application/Espo/Tools/Stream/Service.php @@ -38,6 +38,9 @@ use Espo\Core\ORM\Type\FieldType; use Espo\Entities\StreamSubscription; use Espo\Modules\Crm\Entities\Account; 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\EmailAddress as EmailAddressRepository; use Espo\ORM\Query\Part\Expression as Expr; @@ -358,6 +361,39 @@ class Service $this->entityManager->getQueryExecutor()->execute($delete); } + /** + * Unfollow all portal users from an entity. + * + * @since 9.0.0 + */ + public function unfollowPortalUsersFromEntity(Entity $entity): void + { + if (!$entity->hasId()) { + return; + } + + $delete = $this->entityManager->getQueryBuilder() + ->delete() + ->from(StreamSubscription::ENTITY_TYPE) + ->where([ + 'entityId' => $entity->getId(), + 'entityType' => $entity->getEntityType(), + ]) + ->where( + Condition::in( + Expression::column('userId'), + SelectBuilder::create() + ->from(User::ENTITY_TYPE) + ->select('id') + ->where(['type' => User::TYPE_PORTAL]) + ->build() + ) + ) + ->build(); + + $this->entityManager->getQueryExecutor()->execute($delete); + } + private function loadAssignedUserName(Entity $entity): void { $user = $this->entityManager