From 5efcd6bd8af482db72d7b315a441cc8b118467ab Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 27 Nov 2025 20:53:00 +0200 Subject: [PATCH] follow as collaborator --- .../Resources/i18n/en_US/Preferences.json | 1 + .../Resources/layouts/Preferences/detail.json | 4 ++ .../Resources/metadata/app/aclPortal.json | 3 +- .../metadata/entityDefs/Preferences.json | 4 ++ .../Espo/Tools/Stream/HookProcessor.php | 39 +++++++++-- .../Espo/Tools/User/PreferencesProvider.php | 64 +++++++++++++++++++ 6 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 application/Espo/Tools/User/PreferencesProvider.php diff --git a/application/Espo/Resources/i18n/en_US/Preferences.json b/application/Espo/Resources/i18n/en_US/Preferences.json index 491ea2f192..b518823406 100644 --- a/application/Espo/Resources/i18n/en_US/Preferences.json +++ b/application/Espo/Resources/i18n/en_US/Preferences.json @@ -35,6 +35,7 @@ "followEntityOnStreamPost": "Auto-follow record after posting in Stream", "followCreatedEntities": "Auto-follow created records", "followCreatedEntityTypeList": "Auto-follow created records of specific entity types", + "followAsCollaborator": "Auto-follow when added as a collaborator", "emailUseExternalClient": "Use an external email client", "textSearchStoringDisabled": "Disable text filter storing", "calendarSlotDuration": "Calendar Slot Duration", diff --git a/application/Espo/Resources/layouts/Preferences/detail.json b/application/Espo/Resources/layouts/Preferences/detail.json index fd61a16efe..d1ef1d1920 100644 --- a/application/Espo/Resources/layouts/Preferences/detail.json +++ b/application/Espo/Resources/layouts/Preferences/detail.json @@ -67,6 +67,10 @@ [ {"name": "followCreatedEntities"}, {"name": "followCreatedEntityTypeList"} + ], + [ + {"name": "followAsCollaborator"}, + false ] ] }, diff --git a/application/Espo/Resources/metadata/app/aclPortal.json b/application/Espo/Resources/metadata/app/aclPortal.json index 99a01825e3..0114e9aa3a 100644 --- a/application/Espo/Resources/metadata/app/aclPortal.json +++ b/application/Espo/Resources/metadata/app/aclPortal.json @@ -72,7 +72,8 @@ "assignmentNotificationsIgnoreEntityTypeList": false, "assignmentEmailNotificationsIgnoreEntityTypeList": false, "dashletsOptions": false, - "dashboardLayout": false + "dashboardLayout": false, + "followAsCollaborator": false }, "Call": { "reminders": false diff --git a/application/Espo/Resources/metadata/entityDefs/Preferences.json b/application/Espo/Resources/metadata/entityDefs/Preferences.json index 56bf560e1b..2df3bf862c 100644 --- a/application/Espo/Resources/metadata/entityDefs/Preferences.json +++ b/application/Espo/Resources/metadata/entityDefs/Preferences.json @@ -211,6 +211,10 @@ "default": [], "tooltip": true }, + "followAsCollaborator": { + "type": "bool", + "default": true + }, "emailUseExternalClient": { "type": "bool", "default": false diff --git a/application/Espo/Tools/Stream/HookProcessor.php b/application/Espo/Tools/Stream/HookProcessor.php index 316884f424..8253e459d3 100644 --- a/application/Espo/Tools/Stream/HookProcessor.php +++ b/application/Espo/Tools/Stream/HookProcessor.php @@ -29,6 +29,7 @@ namespace Espo\Tools\Stream; +use Espo\Core\Acl\AssignmentChecker\Helper; use Espo\Core\Field\DateTime; use Espo\Core\Name\Field; use Espo\Core\ORM\Repository\Option\SaveContext; @@ -52,6 +53,7 @@ use Espo\ORM\Repository\Option\SaveOptions; use Espo\Tools\Stream\Service as Service; use Espo\Tools\Stream\Jobs\AutoFollow as AutoFollowJob; use Espo\Tools\Stream\Jobs\ControlFollowers as ControlFollowersJob; +use Espo\Tools\User\PreferencesProvider; /** * Handles operations with entities. @@ -71,7 +73,9 @@ class HookProcessor private Service $service, private User $user, private Preferences $preferences, - private JobSchedulerFactory $jobSchedulerFactory + private JobSchedulerFactory $jobSchedulerFactory, + private Helper $helper, + private PreferencesProvider $preferencesProvider, ) {} public function beforeSave(Entity $entity, SaveOptions $options): void @@ -346,11 +350,11 @@ class HookProcessor if ($entity->isNew()) { $this->afterSaveStreamNew($entity, $options); - - return; + } else { + $this->afterSaveStreamNotNew($entity, $options); } - $this->afterSaveStreamNotNew($entity, $options); + $this->followAddedCollaborators($entity); } /** @@ -680,4 +684,31 @@ class HookProcessor $entity->set(Field::STREAM_UPDATED_AT, DateTime::createNow()->toString()); } + + private function followAddedCollaborators(CoreEntity $entity): void + { + if ( + !$this->helper->hasCollaboratorsField($entity->getEntityType()) || + !$entity->isAttributeChanged(Field::COLLABORATORS . 'Ids') + ) { + return; + } + + $currentUserIds = $entity->getLinkMultipleIdList(Field::COLLABORATORS); + $previousUserIds = $entity->getFetchedLinkMultipleIdList(Field::COLLABORATORS); + + $addedUserIds = array_diff($currentUserIds, $previousUserIds); + + $userIds = array_filter($addedUserIds, fn ($userId) => $this->toFollowUserAsCollaborator($userId)); + $userIds = array_values($userIds); + + $this->service->followEntityMass($entity, $userIds); + } + + private function toFollowUserAsCollaborator(string $userId): bool + { + $preferences = $this->preferencesProvider->tryGet($userId); + + return $preferences?->get('followAsCollaborator') === true; + } } diff --git a/application/Espo/Tools/User/PreferencesProvider.php b/application/Espo/Tools/User/PreferencesProvider.php new file mode 100644 index 0000000000..5571462e75 --- /dev/null +++ b/application/Espo/Tools/User/PreferencesProvider.php @@ -0,0 +1,64 @@ +. + * + * 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\Tools\User; + +use Espo\Entities\Preferences; +use Espo\ORM\EntityManager; +use RuntimeException; + +/** + * @since 9.3.0 + */ +class PreferencesProvider +{ + /** @var array */ + private array $cache = []; + + public function __construct( + private EntityManager $entityManager, + ) {} + + public function tryGet(string $userId): ?Preferences + { + if (!isset($this->cache[$userId])) { + $this->cache[$userId] = $this->entityManager + ->getRepositoryByClass(Preferences::class) + ->getById($userId); + } + + return $this->cache[$userId]; + } + + public function get(string $userId): Preferences + { + return $this->tryGet($userId) ?? + throw new RuntimeException("Could not get preferences for $userId"); + } +}