notification refactoring
This commit is contained in:
@@ -29,21 +29,20 @@
|
||||
|
||||
namespace Espo\Classes\AssignmentNotificators;
|
||||
|
||||
use Espo\{
|
||||
Services\Email as EmailService,
|
||||
Services\Stream as StreamService,
|
||||
Entities\User,
|
||||
ORM\Entity,
|
||||
};
|
||||
use Espo\Services\Email as EmailService;
|
||||
use Espo\Services\Services\Stream as StreamService;
|
||||
|
||||
use Espo\Core\{
|
||||
Notification\AssignmentNotificator,
|
||||
Notification\UserEnabledChecker,
|
||||
Notification\NotificatorParams,
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
AclManager,
|
||||
};
|
||||
use Espo\Core\Notification\AssignmentNotificator;
|
||||
use Espo\Core\Notification\UserEnabledChecker;
|
||||
use Espo\Core\Notification\NotificatorParams;
|
||||
use Espo\Core\ServiceFactory;
|
||||
use Espo\Core\AclManager;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Notification;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
@@ -101,12 +100,12 @@ class Email implements AssignmentNotificator
|
||||
return;
|
||||
}
|
||||
|
||||
$dt = null;
|
||||
|
||||
try {
|
||||
$dt = new DateTime($dateSent);
|
||||
}
|
||||
catch (Exception $e) {}
|
||||
catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$dt) {
|
||||
return;
|
||||
@@ -128,14 +127,14 @@ class Email implements AssignmentNotificator
|
||||
if (
|
||||
!in_array($userId, $userIdList) &&
|
||||
!in_array($userId, $previousUserIdList) &&
|
||||
$userId != $this->user->id
|
||||
$userId !== $this->user->getId()
|
||||
) {
|
||||
$userIdList[] = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'emailId' => $entity->id,
|
||||
'emailId' => $entity->getId(),
|
||||
'emailName' => $entity->get('name'),
|
||||
];
|
||||
|
||||
@@ -159,13 +158,13 @@ class Email implements AssignmentNotificator
|
||||
if ($person) {
|
||||
$data['personEntityType'] = $person->getEntityType();
|
||||
$data['personEntityName'] = $person->get('name');
|
||||
$data['personEntityId'] = $person->id;
|
||||
$data['personEntityId'] = $person->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$userIdFrom = null;
|
||||
|
||||
if ($person && $person->getEntityType() == 'User') {
|
||||
if ($person && $person->getEntityType() === 'User') {
|
||||
$userIdFrom = $person->id;
|
||||
}
|
||||
|
||||
@@ -241,48 +240,48 @@ class Email implements AssignmentNotificator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->get('status') == 'Archived' ||
|
||||
$params->getOption('isBeingImported')
|
||||
) {
|
||||
if ($parent) {
|
||||
if ($this->getStreamService()->checkIsFollowed($parent, $userId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$isArchivedOrBeingImported =
|
||||
$entity->get('status') === 'Archived' ||
|
||||
$params->getOption('isBeingImported');
|
||||
|
||||
if ($account) {
|
||||
if ($this->getStreamService()->checkIsFollowed($account, $userId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
$this->entityManager
|
||||
->getRepository('Notification')
|
||||
->where([
|
||||
'type' => 'EmailReceived',
|
||||
'userId' => $userId,
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => 'Email',
|
||||
])
|
||||
->select(['id'])
|
||||
->findOne()
|
||||
$isArchivedOrBeingImported &&
|
||||
$parent &&
|
||||
$this->getStreamService()->checkIsFollowed($parent, $userId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = $this->entityManager->getEntity('Notification');
|
||||
if (
|
||||
$isArchivedOrBeingImported &&
|
||||
$account &&
|
||||
$this->getStreamService()->checkIsFollowed($account, $userId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification->set([
|
||||
'type' => 'EmailReceived',
|
||||
$existing = $this->entityManager
|
||||
->getRepository(Notification::ENTITY_TYPE)
|
||||
->where([
|
||||
'type' => Notification::TYPE_EMAIL_RECEIVED,
|
||||
'userId' => $userId,
|
||||
'relatedId' => $entity->getId(),
|
||||
'relatedType' => 'Email',
|
||||
])
|
||||
->select(['id'])
|
||||
->findOne();
|
||||
|
||||
if ($existing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->entityManager->createEntity(Notification::ENTITY_TYPE, [
|
||||
'type' => Notification::TYPE_EMAIL_RECEIVED,
|
||||
'userId' => $userId,
|
||||
'data' => $data,
|
||||
'relatedId' => $entity->getId(),
|
||||
'relatedType' => 'Email',
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,10 @@
|
||||
namespace Espo\Core\Notification;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
};
|
||||
use Espo\Entities\Notification;
|
||||
|
||||
class DefaultAssignmentNotificator implements AssignmentNotificator
|
||||
{
|
||||
@@ -101,15 +99,15 @@ class DefaultAssignmentNotificator implements AssignmentNotificator
|
||||
}
|
||||
}
|
||||
else {
|
||||
$isNotSelfAssignment = $assignedUserId !== $this->user->id;
|
||||
$isNotSelfAssignment = $assignedUserId !== $this->user->getId();
|
||||
}
|
||||
|
||||
if (!$isNotSelfAssignment) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->entityManager->createEntity('Notification', [
|
||||
'type' => 'Assign',
|
||||
$this->entityManager->createEntity(Notification::ENTITY_TYPE, [
|
||||
'type' => Notification::TYPE_ASSIGN,
|
||||
'userId' => $assignedUserId,
|
||||
'data' => [
|
||||
'entityType' => $entity->getEntityType(),
|
||||
|
||||
@@ -82,6 +82,26 @@ class Note extends Entity
|
||||
return $this->get('parentId');
|
||||
}
|
||||
|
||||
public function getSuperParentType(): ?string
|
||||
{
|
||||
return $this->get('superParentType');
|
||||
}
|
||||
|
||||
public function getSuperParentId(): ?string
|
||||
{
|
||||
return $this->get('superParentId');
|
||||
}
|
||||
|
||||
public function getRelatedType(): ?string
|
||||
{
|
||||
return $this->get('relatedType');
|
||||
}
|
||||
|
||||
public function getRelatedId(): ?string
|
||||
{
|
||||
return $this->get('relatedId');
|
||||
}
|
||||
|
||||
public function getData(): stdClass
|
||||
{
|
||||
return $this->get('data') ?? (object) [];
|
||||
@@ -92,6 +112,11 @@ class Note extends Entity
|
||||
return (bool) $this->get('isInternal');
|
||||
}
|
||||
|
||||
public function getPost(): ?string
|
||||
{
|
||||
return $this->get('post');
|
||||
}
|
||||
|
||||
public function setAclIsProcessed(): void
|
||||
{
|
||||
$this->aclIsProcessed = true;
|
||||
@@ -142,7 +167,7 @@ class Note extends Entity
|
||||
$this->set('attachmentsTypes', $types);
|
||||
}
|
||||
|
||||
public function addNotifiedUserId($userId)
|
||||
public function addNotifiedUserId($userId): void
|
||||
{
|
||||
$userIdList = $this->get('notifiedUserIdList');
|
||||
|
||||
@@ -157,13 +182,9 @@ class Note extends Entity
|
||||
$this->set('notifiedUserIdList', $userIdList);
|
||||
}
|
||||
|
||||
public function isUserIdNotified($userId)
|
||||
public function isUserIdNotified($userId): bool
|
||||
{
|
||||
$userIdList = $this->get('notifiedUserIdList');
|
||||
|
||||
if (!is_array($userIdList)) {
|
||||
$userIdList = [];
|
||||
}
|
||||
$userIdList = $this->get('notifiedUserIdList') ?? [];
|
||||
|
||||
return in_array($userId, $userIdList);
|
||||
}
|
||||
|
||||
@@ -32,4 +32,18 @@ namespace Espo\Entities;
|
||||
class Notification extends \Espo\Core\ORM\Entity
|
||||
{
|
||||
public const ENTITY_TYPE = 'Notification';
|
||||
|
||||
public const TYPE_ENTITY_REMOVED = 'EntityRemoved';
|
||||
|
||||
public const TYPE_ASSIGN = 'Assign';
|
||||
|
||||
public const TYPE_EMAIL_RECEIVED = 'EmailReceived';
|
||||
|
||||
public const TYPE_NOTE = 'Note';
|
||||
|
||||
public const TYPE_MENTION_IN_POST = 'MentionInPost';
|
||||
|
||||
public const TYPE_MESSAGE = 'Message';
|
||||
|
||||
public const TYPE_SYSTEM = 'System';
|
||||
}
|
||||
|
||||
@@ -29,183 +29,44 @@
|
||||
|
||||
namespace Espo\Hooks\Common;
|
||||
|
||||
use Espo\{
|
||||
ORM\Entity,
|
||||
Services\Stream as StreamService,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
Utils\Config,
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
Notification\AssignmentNotificator,
|
||||
Notification\AssignmentNotificatorFactory,
|
||||
Notification\NotificatorParams,
|
||||
};
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\Notification\HookProcessor;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Notifications
|
||||
{
|
||||
public static $order = 10;
|
||||
|
||||
private $notifatorsHash = [];
|
||||
private $processor;
|
||||
|
||||
private $streamService;
|
||||
|
||||
private $hasStreamCache = [];
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $serviceFactory;
|
||||
|
||||
private $notificatorFactory;
|
||||
|
||||
private $user;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
Config $config,
|
||||
EntityManager $entityManager,
|
||||
ServiceFactory $serviceFactory,
|
||||
AssignmentNotificatorFactory $notificatorFactory,
|
||||
User $user
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
$this->notificatorFactory = $notificatorFactory;
|
||||
$this->user = $user;
|
||||
public function __construct(HookProcessor $processor)
|
||||
{
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
public function afterSave(Entity $entity, array $options = []): void
|
||||
public function afterSave(Entity $entity, array $options): void
|
||||
{
|
||||
if (!empty($options['silent']) || !empty($options['noNotifications'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
/**
|
||||
* No need to process assignment notifications for entity types that have Stream enabled.
|
||||
* Users are notified via Stream notifications.
|
||||
*/
|
||||
if ($this->checkHasStream($entityType) && !$entity->hasLinkMultipleField('assignedUsers')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assignmentNotificationsEntityList = $this->config->get('assignmentNotificationsEntityList', []);
|
||||
|
||||
if (!in_array($entityType, $assignmentNotificationsEntityList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notificator = $this->getNotificator($entityType);
|
||||
|
||||
if (!$notificator instanceof AssignmentNotificator) {
|
||||
// For backward compatiblity.
|
||||
$notificator->process($entity, $options);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$params = NotificatorParams::create()->withRawOptions($options);
|
||||
|
||||
$notificator->process($entity, $params);
|
||||
$this->processor->afterSave($entity, $options);
|
||||
}
|
||||
|
||||
public function beforeRemove(Entity $entity, array $options = []): void
|
||||
public function beforeRemove(Entity $entity, array $options): void
|
||||
{
|
||||
if (!empty($options['silent']) || !empty($options['noNotifications'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if ($this->checkHasStream($entityType)) {
|
||||
$followersData = $this->getStreamService()->getEntityFollowers($entity);
|
||||
|
||||
foreach ($followersData['idList'] as $userId) {
|
||||
if ($userId === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = $this->entityManager->getEntity('Notification');
|
||||
|
||||
$notification->set([
|
||||
'userId' => $userId,
|
||||
'type' => 'EntityRemoved',
|
||||
'data' => [
|
||||
'entityType' => $entity->getEntityType(),
|
||||
'entityId' => $entity->id,
|
||||
'entityName' => $entity->get('name'),
|
||||
'userId' => $this->user->id,
|
||||
'userName' => $this->user->get('name'),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
}
|
||||
$this->processor->beforeRemove($entity);
|
||||
}
|
||||
|
||||
public function afterRemove(Entity $entity): void
|
||||
public function afterRemove(Entity $entity, array $options): void
|
||||
{
|
||||
$query = $this->entityManager->getQueryBuilder()
|
||||
->delete()
|
||||
->from('Notification')
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entity->getEntityType(),
|
||||
],
|
||||
[
|
||||
'relatedParentId' => $entity->id,
|
||||
'relatedParentType' => $entity->getEntityType(),
|
||||
],
|
||||
],
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
|
||||
private function checkHasStream($entityType): bool
|
||||
{
|
||||
if (!array_key_exists($entityType, $this->hasStreamCache)) {
|
||||
$this->hasStreamCache[$entityType] = (bool) $this->metadata->get("scopes.{$entityType}.stream");
|
||||
if (!empty($options['silent'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->hasStreamCache[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AssignmentNotificator
|
||||
*/
|
||||
private function getNotificator(string $entityType): object
|
||||
{
|
||||
if (empty($this->notifatorsHash[$entityType])) {
|
||||
$notificator = $this->notificatorFactory->create($entityType);
|
||||
|
||||
$this->notifatorsHash[$entityType] = $notificator;
|
||||
}
|
||||
|
||||
return $this->notifatorsHash[$entityType];
|
||||
}
|
||||
|
||||
private function getStreamService(): StreamService
|
||||
{
|
||||
if (empty($this->streamService)) {
|
||||
$this->streamService = $this->serviceFactory->create('Stream');
|
||||
}
|
||||
|
||||
return $this->streamService;
|
||||
$this->processor->afterRemove($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,157 +29,26 @@
|
||||
|
||||
namespace Espo\Hooks\Note;
|
||||
|
||||
use Espo\Tools\Notification\NoteMentionHookProcessor;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
AclManager,
|
||||
Acl,
|
||||
};
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Mentions
|
||||
{
|
||||
public static $order = 9;
|
||||
|
||||
protected $notificationService = null;
|
||||
private $processor;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $serviceFactory;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $aclManager;
|
||||
|
||||
protected $acl;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
ServiceFactory $serviceFactory,
|
||||
User $user,
|
||||
AclManager $aclManager,
|
||||
Acl $acl
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
$this->user = $user;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->acl = $acl;
|
||||
public function __construct(NoteMentionHookProcessor $processor)
|
||||
{
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
protected function addMentionData($entity)
|
||||
public function beforeSave(Entity $entity, array $options): void
|
||||
{
|
||||
$post = $entity->get('post');
|
||||
|
||||
$mentionData = (object) [];
|
||||
|
||||
$previousMentionList = [];
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
$data = $entity->get('data');
|
||||
|
||||
if (!empty($data) && !empty($data->mentions)) {
|
||||
$previousMentionList = array_keys(get_object_vars($data->mentions));
|
||||
}
|
||||
}
|
||||
|
||||
preg_match_all('/(@[\w@.-]+)/', $post, $matches);
|
||||
|
||||
$mentionCount = 0;
|
||||
|
||||
if (is_array($matches) && !empty($matches[0]) && is_array($matches[0])) {
|
||||
$parent = null;
|
||||
|
||||
if ($entity->get('parentId') && $entity->get('parentType')) {
|
||||
$parent = $this->entityManager
|
||||
->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
}
|
||||
|
||||
foreach ($matches[0] as $item) {
|
||||
$userName = substr($item, 1);
|
||||
|
||||
$user = $this->entityManager
|
||||
->getRepository('User')
|
||||
->where(['userName' => $userName])
|
||||
->findOne();
|
||||
|
||||
if ($user) {
|
||||
if (!$this->acl->checkUserPermission($user, 'assignment')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$m = [
|
||||
'id' => $user->id,
|
||||
'name' => $user->get('name'),
|
||||
'userName' => $user->get('userName'),
|
||||
'_scope' => $user->getEntityType(),
|
||||
];
|
||||
|
||||
$mentionData->$item = (object) $m;
|
||||
$mentionCount++;
|
||||
|
||||
if (!in_array($item, $previousMentionList)) {
|
||||
if ($user->id == $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->notifyAboutMention($entity, $user, $parent);
|
||||
|
||||
$entity->addNotifiedUserId($user->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = $entity->get('data');
|
||||
|
||||
if (empty($data)) {
|
||||
$data = (object) [];
|
||||
}
|
||||
|
||||
if ($mentionCount) {
|
||||
$data->mentions = $mentionData;
|
||||
}
|
||||
else {
|
||||
unset($data->mentions);
|
||||
}
|
||||
|
||||
$entity->set('data', $data);
|
||||
}
|
||||
|
||||
public function beforeSave(Entity $entity)
|
||||
{
|
||||
if ($entity->get('type') == 'Post') {
|
||||
$post = $entity->get('post');
|
||||
|
||||
$this->addMentionData($entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected function notifyAboutMention(Entity $entity, User $user, Entity $parent = null)
|
||||
{
|
||||
if ($user->isPortal()) {
|
||||
if (!empty($options['silent'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
if (!$this->aclManager->check($user, $parent, 'stream')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getNotificationService()->notifyAboutMentionInPost($user->id, $entity->id);
|
||||
}
|
||||
|
||||
protected function getNotificationService()
|
||||
{
|
||||
if (empty($this->notificationService)) {
|
||||
$this->notificationService = $this->serviceFactory->create('Notification');
|
||||
}
|
||||
|
||||
return $this->notificationService;
|
||||
$this->processor->beforeSave($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,345 +30,25 @@
|
||||
namespace Espo\Hooks\Note;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
ServiceFactory,
|
||||
AclManager as InternalAclManager,
|
||||
Utils\Metadata,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use StdClass;
|
||||
use Espo\Tools\Notification\NoteHookProcessor;
|
||||
|
||||
class Notifications
|
||||
{
|
||||
protected $notificationService = null;
|
||||
|
||||
protected $streamService = null;
|
||||
|
||||
public static $order = 14;
|
||||
|
||||
protected $metadata;
|
||||
private $processor;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $serviceFactory;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $internalAclManager;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
EntityManager $entityManager,
|
||||
ServiceFactory $serviceFactory,
|
||||
User $user,
|
||||
InternalAclManager $internalAclManager
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
$this->user = $user;
|
||||
$this->internalAclManager = $internalAclManager;
|
||||
}
|
||||
|
||||
protected function getMentionedUserIdList($entity)
|
||||
public function __construct(NoteHookProcessor $processor)
|
||||
{
|
||||
$mentionedUserList = [];
|
||||
$data = $entity->get('data');
|
||||
|
||||
if (($data instanceof StdClass) && ($data->mentions instanceof StdClass)) {
|
||||
$mentions = get_object_vars($data->mentions);
|
||||
|
||||
foreach ($mentions as $d) {
|
||||
$mentionedUserList[] = $d->id;
|
||||
}
|
||||
}
|
||||
|
||||
return $mentionedUserList;
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
protected function getSubscriberList(string $parentType, string $parentId, bool $isInternal = false)
|
||||
{
|
||||
return $this->getStreamService()->getSubscriberList($parentType, $parentId, $isInternal);
|
||||
}
|
||||
|
||||
public function afterSave(Entity $entity, array $options = [])
|
||||
public function afterSave(Entity $entity, array $options): void
|
||||
{
|
||||
if (!$entity->isNew() && empty($options['forceProcessNotifications'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parentType = $entity->get('parentType');
|
||||
$parentId = $entity->get('parentId');
|
||||
$superParentType = $entity->get('superParentType');
|
||||
$superParentId = $entity->get('superParentId');
|
||||
|
||||
$notifyUserIdList = [];
|
||||
|
||||
if ($parentType && $parentId) {
|
||||
$userList = $this->getSubscriberList($parentType, $parentId, $entity->get('isInternal'));
|
||||
|
||||
$userIdMetList = [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userIdMetList[] = $user->id;
|
||||
}
|
||||
|
||||
if ($superParentType && $superParentId) {
|
||||
$additionalUserList = $this
|
||||
->getSubscriberList($superParentType, $superParentId, $entity->get('isInternal'));
|
||||
|
||||
foreach ($additionalUserList as $user) {
|
||||
if ($user->isPortal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($user->id, $userIdMetList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userIdMetList[] = $user->id;
|
||||
$userList[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->get('relatedType')) {
|
||||
$targetType = $entity->get('relatedType');
|
||||
}
|
||||
else {
|
||||
$targetType = $parentType;
|
||||
}
|
||||
|
||||
$skipAclCheck = false;
|
||||
|
||||
if (!$entity->isAclProcessed()) {
|
||||
$skipAclCheck = true;
|
||||
}
|
||||
else {
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
$userIdList = $entity->getLinkMultipleIdList('users');
|
||||
}
|
||||
|
||||
foreach ($userList as $user) {
|
||||
if ($skipAclCheck) {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isAdmin()) {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
if ($entity->get('relatedType')) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $this->internalAclManager->getLevel($user, $targetType, 'read');
|
||||
|
||||
if ($level === 'all') {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
else if ($level === 'team') {
|
||||
if (in_array($user->id, $userIdList)) {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($teamIdList)) {
|
||||
$userTeamIdList = $user->getLinkMultipleIdList('teams');
|
||||
|
||||
foreach ($teamIdList as $teamId) {
|
||||
if (in_array($teamId, $userTeamIdList)) {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
} else if ($level === 'own') {
|
||||
if (in_array($user->id, $userIdList)) {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$targetType = $entity->get('targetType');
|
||||
|
||||
if ($targetType === 'users') {
|
||||
$targetUserIdList = $entity->get('usersIds');
|
||||
|
||||
if (is_array($targetUserIdList)) {
|
||||
foreach ($targetUserIdList as $userId) {
|
||||
if ($userId === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($userId, $notifyUserIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($targetType === 'teams') {
|
||||
$targetTeamIdList = $entity->get('teamsIds');
|
||||
|
||||
if (is_array($targetTeamIdList)) {
|
||||
foreach ($targetTeamIdList as $teamId) {
|
||||
$team = $this->entityManager->getEntity('Team', $teamId);
|
||||
|
||||
if (!$team) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository('Team')
|
||||
->getRelation($team, 'users')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->id === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($user->id, $notifyUserIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($targetType === 'portals') {
|
||||
$targetPortalIdList = $entity->get('portalsIds');
|
||||
|
||||
if (is_array($targetPortalIdList)) {
|
||||
foreach ($targetPortalIdList as $portalId) {
|
||||
$portal = $this->entityManager->getEntity('Portal', $portalId);
|
||||
|
||||
if (!$portal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository('Portal')
|
||||
->getRelation($portal, 'users')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->id === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($user->id, $notifyUserIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($targetType === 'all') {
|
||||
$targetUserList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
'type' => ['regular', 'admin'],
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->id === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$notifyUserIdList = array_unique($notifyUserIdList);
|
||||
|
||||
foreach ($notifyUserIdList as $i => $userId) {
|
||||
if ($entity->isUserIdNotified($userId)) {
|
||||
unset($notifyUserIdList[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if (
|
||||
$this->entityManager
|
||||
->getRepository('Notification')
|
||||
->select(['id'])
|
||||
->where([
|
||||
'type' => 'Note',
|
||||
'relatedType' => 'Note',
|
||||
'relatedId' => $entity->id,
|
||||
'userId' => $userId,
|
||||
])
|
||||
->findOne()
|
||||
) {
|
||||
unset($notifyUserIdList[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$notifyUserIdList = array_values($notifyUserIdList);
|
||||
|
||||
if (!empty($notifyUserIdList)) {
|
||||
$this->getNotificationService()->notifyAboutNote($notifyUserIdList, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getNotificationService()
|
||||
{
|
||||
if (empty($this->notificationService)) {
|
||||
$this->notificationService = $this->serviceFactory->create('Notification');
|
||||
}
|
||||
|
||||
return $this->notificationService;
|
||||
}
|
||||
|
||||
protected function getStreamService()
|
||||
{
|
||||
if (empty($this->streamService)) {
|
||||
$this->streamService = $this->serviceFactory->create('Stream');
|
||||
}
|
||||
|
||||
return $this->streamService;
|
||||
$this->processor->afterSave($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ class Note extends Record
|
||||
$portalIdList = $entity->getLinkMultipleIdList('portals');
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
|
||||
$targetUserList = null;
|
||||
$targetUserList = [];
|
||||
|
||||
if ($targetType === NoteEntity::TARGET_USERS) {
|
||||
$targetUserList = $this->entityManager
|
||||
|
||||
@@ -29,177 +29,50 @@
|
||||
|
||||
namespace Espo\Services;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Core\{
|
||||
Record\Collection as RecordCollection,
|
||||
Exceptions\Error,
|
||||
};
|
||||
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Services\Stream\NoteAccessControl;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
class Notification extends \Espo\Services\Record implements
|
||||
|
||||
Di\WebSocketSubmissionAware
|
||||
class Notification extends \Espo\Services\Record
|
||||
{
|
||||
use Di\WebSocketSubmissionSetter;
|
||||
|
||||
protected $actionHistoryDisabled = true;
|
||||
|
||||
private $noteAccessControl = null;
|
||||
|
||||
public function notifyAboutMentionInPost(string $userId, string $noteId): void
|
||||
{
|
||||
$notification = $this->entityManager->getEntity('Notification');
|
||||
|
||||
$notification->set([
|
||||
'type' => 'MentionInPost',
|
||||
'data' => ['noteId' => $noteId],
|
||||
'userId' => $userId,
|
||||
'relatedId' => $noteId,
|
||||
'relatedType' => 'Note',
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
|
||||
public function notifyAboutNote(array $userIdList, Note $note): void
|
||||
{
|
||||
$data = ['noteId' => $note->id];
|
||||
|
||||
$related = null;
|
||||
|
||||
if ($note->get('relatedType') == 'Email') {
|
||||
$related = $this->entityManager
|
||||
->getRepository('Email')
|
||||
->select(['id', 'sentById', 'createdById'])
|
||||
->where(['id' => $note->get('relatedId')])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getCollectionFactory()
|
||||
->create();
|
||||
|
||||
$userList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->select(['id', 'type'])
|
||||
->where([
|
||||
'isActive' => true,
|
||||
'id' => $userIdList,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userId = $user->id;
|
||||
|
||||
if (!$this->checkUserNoteAccess($user, $note)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($note->get('createdById') === $user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($related && $related->getEntityType() == 'Email' && $related->get('sentById') == $user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($related && $related->get('createdById') == $user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = $this->entityManager->getEntity('Notification');
|
||||
|
||||
$notification->set([
|
||||
'id' => Util::generateId(),
|
||||
'data' => $data,
|
||||
'type' => 'Note',
|
||||
'userId' => $userId,
|
||||
'createdAt' => $now,
|
||||
'relatedId' => $note->id,
|
||||
'relatedType' => 'Note',
|
||||
'relatedParentId' => $note->get('parentId'),
|
||||
'relatedParentType' => $note->get('parentType'),
|
||||
]);
|
||||
|
||||
$collection[] = $notification;
|
||||
}
|
||||
|
||||
if (empty($collection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->entityManager->getMapper()->massInsert($collection);
|
||||
|
||||
if ($this->getConfig()->get('useWebSocket')) {
|
||||
foreach ($userIdList as $userId) {
|
||||
$this->webSocketSubmission->submit('newNotification', $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function checkUserNoteAccess(User $user, Note $note): bool
|
||||
{
|
||||
if ($user->isPortal()) {
|
||||
if ($note->get('relatedType')) {
|
||||
if ($note->get('relatedType') === 'Email' && $note->get('parentType') === 'Case') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($note->get('relatedType')) {
|
||||
if (!$this->getAclManager()->checkScope($user, $note->get('relatedType'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($note->get('parentType')) {
|
||||
if (!$this->getAclManager()->checkScope($user, $note->get('parentType'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getNotReadCount(string $userId): int
|
||||
{
|
||||
$whereClause = array(
|
||||
$whereClause = [
|
||||
'userId' => $userId,
|
||||
'read' => 0
|
||||
);
|
||||
'read' => false,
|
||||
];
|
||||
|
||||
$ignoreScopeList = $this->getIgnoreScopeList();
|
||||
if (!empty($ignoreScopeList)) {
|
||||
$where = [];
|
||||
$where[] = array(
|
||||
'OR' => array(
|
||||
|
||||
if (count($ignoreScopeList)) {
|
||||
$whereClause[] = [
|
||||
'OR' => [
|
||||
'relatedParentType' => null,
|
||||
'relatedParentType!=' => $ignoreScopeList
|
||||
)
|
||||
);
|
||||
$whereClause[] = $where;
|
||||
'relatedParentType!=' => $ignoreScopeList,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $this->entityManager->getRepository('Notification')->where($whereClause)->count();
|
||||
return $this->entityManager
|
||||
->getRDBRepository('Notification')
|
||||
->where($whereClause)
|
||||
->count();
|
||||
}
|
||||
|
||||
public function markAllRead(string $userId)
|
||||
{
|
||||
$update = $this->entityManager->getQueryBuilder()->update()
|
||||
$update = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->update()
|
||||
->in('Notification')
|
||||
->set(['read' => true])
|
||||
->where([
|
||||
@@ -359,7 +232,7 @@ class Notification extends \Espo\Services\Record implements
|
||||
return new RecordCollection($collection, $count);
|
||||
}
|
||||
|
||||
protected function getIgnoreScopeList(): array
|
||||
private function getIgnoreScopeList(): array
|
||||
{
|
||||
$ignoreScopeList = [];
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Notification;
|
||||
|
||||
use Espo\Core\Notification\AssignmentNotificatorFactory;
|
||||
use Espo\Core\Notification\AssignmentNotificator;
|
||||
use Espo\Core\Notification\NotificatorParams;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
use Espo\Services\Stream as StreamService;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Notification;
|
||||
|
||||
/**
|
||||
* Handles operations with entities.
|
||||
*/
|
||||
class HookProcessor
|
||||
{
|
||||
private $notifatorsHash = [];
|
||||
|
||||
private $hasStreamCache = [];
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $streamService;
|
||||
|
||||
private $notificatorFactory;
|
||||
|
||||
private $user;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
Config $config,
|
||||
EntityManager $entityManager,
|
||||
StreamService $streamService,
|
||||
AssignmentNotificatorFactory $notificatorFactory,
|
||||
User $user
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->streamService = $streamService;
|
||||
$this->notificatorFactory = $notificatorFactory;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function afterSave(Entity $entity, array $options): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
/**
|
||||
* No need to process assignment notifications for entity types that have Stream enabled.
|
||||
* Users are notified via Stream notifications.
|
||||
*/
|
||||
if ($this->checkHasStream($entityType) && !$entity->hasLinkMultipleField('assignedUsers')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assignmentNotificationsEntityList = $this->config->get('assignmentNotificationsEntityList') ?? [];
|
||||
|
||||
if (!in_array($entityType, $assignmentNotificationsEntityList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notificator = $this->getNotificator($entityType);
|
||||
|
||||
if (!$notificator instanceof AssignmentNotificator) {
|
||||
// For backward compatiblity.
|
||||
$notificator->process($entity, $options);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$params = NotificatorParams::create()->withRawOptions($options);
|
||||
|
||||
$notificator->process($entity, $params);
|
||||
}
|
||||
|
||||
public function beforeRemove(Entity $entity): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (!$this->checkHasStream($entityType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$followersData = $this->streamService->getEntityFollowers($entity);
|
||||
|
||||
$userIdList = $followersData['idList'];
|
||||
|
||||
foreach ($userIdList as $userId) {
|
||||
if ($userId === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->entityManager->createEntity(Notification::ENTITY_TYPE, [
|
||||
'userId' => $userId,
|
||||
'type' => Notification::TYPE_ENTITY_REMOVED,
|
||||
'data' => [
|
||||
'entityType' => $entity->getEntityType(),
|
||||
'entityId' => $entity->getId(),
|
||||
'entityName' => $entity->get('name'),
|
||||
'userId' => $this->user->getId(),
|
||||
'userName' => $this->user->get('name'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function afterRemove(Entity $entity): void
|
||||
{
|
||||
$query = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Notification::ENTITY_TYPE)
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedId' => $entity->getId(),
|
||||
'relatedType' => $entity->getEntityType(),
|
||||
],
|
||||
[
|
||||
'relatedParentId' => $entity->getId(),
|
||||
'relatedParentType' => $entity->getEntityType(),
|
||||
],
|
||||
],
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
|
||||
private function checkHasStream(string $entityType): bool
|
||||
{
|
||||
if (!array_key_exists($entityType, $this->hasStreamCache)) {
|
||||
$this->hasStreamCache[$entityType] =
|
||||
(bool) $this->metadata->get(['scopes', $entityType, 'stream']);
|
||||
}
|
||||
|
||||
return $this->hasStreamCache[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AssignmentNotificator
|
||||
*/
|
||||
private function getNotificator(string $entityType): object
|
||||
{
|
||||
if (empty($this->notifatorsHash[$entityType])) {
|
||||
$notificator = $this->notificatorFactory->create($entityType);
|
||||
|
||||
$this->notifatorsHash[$entityType] = $notificator;
|
||||
}
|
||||
|
||||
return $this->notifatorsHash[$entityType];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Notification;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\AclManager as InternalAclManager;
|
||||
use Espo\Core\Acl\Table;
|
||||
|
||||
use Espo\Services\Stream as StreamService;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Collection;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\Entities\Notification;
|
||||
use Espo\Entities\Note;
|
||||
|
||||
/**
|
||||
* Handles notifications after note saving.
|
||||
*/
|
||||
class NoteHookProcessor
|
||||
{
|
||||
private $streamService;
|
||||
|
||||
private $service;
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $user;
|
||||
|
||||
private $internalAclManager;
|
||||
|
||||
public function __construct(
|
||||
StreamService $streamService,
|
||||
Service $service,
|
||||
Metadata $metadata,
|
||||
EntityManager $entityManager,
|
||||
User $user,
|
||||
InternalAclManager $internalAclManager
|
||||
) {
|
||||
$this->streamService = $streamService;
|
||||
$this->service = $service;
|
||||
$this->metadata = $metadata;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
$this->internalAclManager = $internalAclManager;
|
||||
}
|
||||
|
||||
public function afterSave(Note $note): void
|
||||
{
|
||||
if ($note->getParentType() && $note->getParentId()) {
|
||||
$this->afterSaveParent($note);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->afterSaveNoParent($note);
|
||||
}
|
||||
|
||||
private function afterSaveParent(Note $note): void
|
||||
{
|
||||
$parentType = $note->getParentType();
|
||||
$parentId = $note->getParentId();
|
||||
$superParentType = $note->getSuperParentType();
|
||||
$superParentId = $note->getSuperParentId();
|
||||
|
||||
$userList = $this->getSubscriberList($parentType, $parentId, $note->isInternal());
|
||||
|
||||
$userIdMetList = [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userIdMetList[] = $user->getId();
|
||||
}
|
||||
|
||||
if ($superParentType && $superParentId) {
|
||||
$additionalUserList = $this->getSubscriberList(
|
||||
$superParentType,
|
||||
$superParentId,
|
||||
$note->isInternal()
|
||||
);
|
||||
|
||||
foreach ($additionalUserList as $user) {
|
||||
if (
|
||||
$user->isPortal() ||
|
||||
in_array($user->getId(), $userIdMetList)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userIdMetList[] = $user->getId();
|
||||
$userList[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
$targetType = $note->getRelatedType() ? $note->getRelatedType() : $parentType;
|
||||
|
||||
// This is correct.
|
||||
$skipAclCheck = !$note->isAclProcessed();
|
||||
|
||||
if (!$skipAclCheck) {
|
||||
$teamIdList = $note->getLinkMultipleIdList('teams');
|
||||
$userIdList = $note->getLinkMultipleIdList('users');
|
||||
}
|
||||
|
||||
foreach ($userList as $user) {
|
||||
if ($skipAclCheck) {
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isAdmin()) {
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal() && $note->getRelatedType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $this->internalAclManager->getLevel($user, $targetType, Table::ACTION_READ);
|
||||
|
||||
if (!$this->checkUserAccess($user, $level, $teamIdList, $userIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
}
|
||||
|
||||
$this->processNotify($note, array_unique($notifyUserIdList));
|
||||
}
|
||||
|
||||
private function afterSaveNoParent(Note $note): void
|
||||
{
|
||||
$targetType = $note->getTargetType();
|
||||
|
||||
if ($targetType === Note::TARGET_USERS) {
|
||||
$this->afterSaveTargetUsers($note);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_TEAMS) {
|
||||
$this->afterSaveTargetTeams($note);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_PORTALS) {
|
||||
$this->afterSaveTargetPortals($note);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($targetType === Note::TARGET_ALL) {
|
||||
$this->afterSaveTargetAll($note);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function processNotify(Note $note, array $userIdList): void
|
||||
{
|
||||
$filteredUserIdList = array_filter(
|
||||
$userIdList,
|
||||
function (string $userId) use ($note) {
|
||||
if ($note->isUserIdNotified($userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($note->isNew()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$existing = $this->entityManager
|
||||
->getRDBRepository(Notification::ENTITY_TYPE)
|
||||
->select(['id'])
|
||||
->where([
|
||||
'type' => Notification::TYPE_NOTE,
|
||||
'relatedType' => Note::ENTITY_TYPE,
|
||||
'relatedId' => $note->getId(),
|
||||
'userId' => $userId,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($existing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
if (!count($filteredUserIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->service->notifyAboutNote($filteredUserIdList, $note);
|
||||
}
|
||||
|
||||
private function afterSaveTargetUsers(Note $note): void
|
||||
{
|
||||
$targetUserIdList = $note->get('usersIds') ?? [];
|
||||
|
||||
if (!count($targetUserIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifyUserIdList = [];
|
||||
|
||||
foreach ($targetUserIdList as $userId) {
|
||||
if ($userId === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $userId;
|
||||
}
|
||||
|
||||
$this->processNotify($note, array_unique($notifyUserIdList));
|
||||
}
|
||||
|
||||
private function afterSaveTargetTeams(Note $note): void
|
||||
{
|
||||
$targetTeamIdList = $note->get('teamsIds') ?? [];
|
||||
|
||||
if (!count($targetTeamIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($targetTeamIdList as $teamId) {
|
||||
$team = $this->entityManager->getEntity(Team::ENTITY_TYPE, $teamId);
|
||||
|
||||
if (!$team) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository(Team::ENTITY_TYPE)
|
||||
->getRelation($team, 'users')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
])
|
||||
->select('id')
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$this->processNotify($note, array_unique($notifyUserIdList));
|
||||
}
|
||||
|
||||
private function afterSaveTargetPortals(Note $note): void
|
||||
{
|
||||
$targetPortalIdList = $note->get('portalsIds') ?? [];
|
||||
|
||||
if (!count($targetPortalIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifyUserIdList = [];
|
||||
|
||||
foreach ($targetPortalIdList as $portalId) {
|
||||
$portal = $this->entityManager->getEntity(Portal::ENTITY_TYPE, $portalId);
|
||||
|
||||
if (!$portal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository(Portal::ENTITY_TYPE)
|
||||
->getRelation($portal, 'users')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
])
|
||||
->select(['id'])
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$this->processNotify($note, array_unique($notifyUserIdList));
|
||||
}
|
||||
|
||||
private function afterSaveTargetAll(Note $note): void
|
||||
{
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->where([
|
||||
'isActive' => true,
|
||||
'type' => ['regular', 'admin'],
|
||||
])
|
||||
->select('id')
|
||||
->find();
|
||||
|
||||
$notifyUserIdList = [];
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->getId();
|
||||
}
|
||||
|
||||
$this->processNotify($note, $notifyUserIdList);
|
||||
}
|
||||
|
||||
private function checkUserAccess(
|
||||
User $user,
|
||||
string $level,
|
||||
array $teamIdList,
|
||||
array $userIdList
|
||||
): bool {
|
||||
|
||||
if ($level === Table::LEVEL_ALL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_TEAM) {
|
||||
if (in_array($user->getId(), $userIdList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!count($teamIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$userTeamIdList = $user->getLinkMultipleIdList('teams');
|
||||
|
||||
foreach ($teamIdList as $teamId) {
|
||||
if (in_array($teamId, $userTeamIdList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_OWN) {
|
||||
return in_array($user->getId(), $userIdList);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
private function getSubscriberList(string $parentType, string $parentId, bool $isInternal = false): Collection
|
||||
{
|
||||
return $this->streamService->getSubscriberList($parentType, $parentId, $isInternal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Notification;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\AclManager;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Note;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class NoteMentionHookProcessor
|
||||
{
|
||||
private $service;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $user;
|
||||
|
||||
private $acl;
|
||||
|
||||
private $aclManager;
|
||||
|
||||
public function __construct(
|
||||
Service $service,
|
||||
EntityManager $entityManager,
|
||||
User $user,
|
||||
Acl $acl,
|
||||
AclManager $aclManager
|
||||
) {
|
||||
$this->service = $service;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
$this->acl = $acl;
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
|
||||
public function beforeSave(Note $note): void
|
||||
{
|
||||
if ($note->getType() !== Note::TYPE_POST) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->process($note);
|
||||
}
|
||||
|
||||
private function process(Note $note): void
|
||||
{
|
||||
$post = $note->getPost() ?? '';
|
||||
|
||||
$mentionData = (object) [];
|
||||
|
||||
$previousMentionList = [];
|
||||
|
||||
if (!$note->isNew()) {
|
||||
$previousMentionList = array_keys(
|
||||
get_object_vars($note->getData()->mentions ?? (object) [])
|
||||
);
|
||||
}
|
||||
|
||||
$matches = null;
|
||||
|
||||
preg_match_all('/(@[\w@.-]+)/', $post, $matches);
|
||||
|
||||
$mentionCount = 0;
|
||||
|
||||
if (is_array($matches) && !empty($matches[0]) && is_array($matches[0])) {
|
||||
$mentionCount = $this->processMatches($matches[0], $note, $mentionData, $previousMentionList);
|
||||
}
|
||||
|
||||
$data = $note->getData();
|
||||
|
||||
if ($mentionCount) {
|
||||
$data->mentions = $mentionData;
|
||||
}
|
||||
else {
|
||||
unset($data->mentions);
|
||||
}
|
||||
|
||||
$note->set('data', $data);
|
||||
}
|
||||
|
||||
private function processMatches(
|
||||
array $matchList,
|
||||
Note $note,
|
||||
stdClass $mentionData,
|
||||
array $previousMentionList
|
||||
): int {
|
||||
|
||||
$mentionCount = 0;
|
||||
|
||||
$parent = $note->getParentId() && $note->getParentType() ?
|
||||
$this->entityManager->getEntity(
|
||||
$note->getParentType(),
|
||||
$note->getParentId()
|
||||
) :
|
||||
null;
|
||||
|
||||
foreach ($matchList as $item) {
|
||||
$userName = substr($item, 1);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->where([
|
||||
'userName' => $userName,
|
||||
'isActive' => true,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->acl->checkUserPermission($user, 'assignment')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mentionData->$item = (object) [
|
||||
'id' => $user->getId(),
|
||||
'name' => $user->get('name'),
|
||||
'userName' => $user->get('userName'),
|
||||
'_scope' => $user->getEntityType(),
|
||||
];
|
||||
|
||||
$mentionCount++;
|
||||
|
||||
if (in_array($item, $previousMentionList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($parent && !$this->aclManager->checkEntityStream($user, $parent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$note->addNotifiedUserId($user->getId());
|
||||
|
||||
$this->service->notifyAboutMentionInPost($user->getId(), $note);
|
||||
}
|
||||
|
||||
return $mentionCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Notification;
|
||||
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\Notification;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Email;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\WebSocket\Submission;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class Service
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
private $config;
|
||||
|
||||
private $aclManager;
|
||||
|
||||
private $webSocketSubmission;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
Config $config,
|
||||
AclManager $aclManager,
|
||||
Submission $webSocketSubmission
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->config = $config;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->webSocketSubmission = $webSocketSubmission;
|
||||
}
|
||||
|
||||
public function notifyAboutMentionInPost(string $userId, Note $note): void
|
||||
{
|
||||
$this->entityManager->createEntity(Notification::ENTITY_TYPE, [
|
||||
'type' => Notification::TYPE_MENTION_IN_POST,
|
||||
'data' => [
|
||||
'noteId' => $note->getId(),
|
||||
],
|
||||
'userId' => $userId,
|
||||
'relatedId' => $note->getId(),
|
||||
'relatedType' => Note::ENTITY_TYPE,
|
||||
]);
|
||||
}
|
||||
|
||||
public function notifyAboutNote(array $userIdList, Note $note): void
|
||||
{
|
||||
$related = null;
|
||||
|
||||
if ($note->getRelatedType() === Email::ENTITY_TYPE) {
|
||||
$related = $this->entityManager
|
||||
->getRDBRepository(Email::ENTITY_TYPE)
|
||||
->select(['id', 'sentById', 'createdById'])
|
||||
->where(['id' => $note->getRelatedId()])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
$now = date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getCollectionFactory()
|
||||
->create();
|
||||
|
||||
$userList = $this->entityManager
|
||||
->getRDBRepository('User')
|
||||
->select(['id', 'type'])
|
||||
->where([
|
||||
'isActive' => true,
|
||||
'id' => $userIdList,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userId = $user->getId();
|
||||
|
||||
if (!$this->checkUserNoteAccess($user, $note)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($note->get('createdById') === $user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$related &&
|
||||
$related->getEntityType() === Email::ENTITY_TYPE &&
|
||||
$related->get('sentById') === $user->getId()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($related && $related->get('createdById') === $user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = $this->entityManager->getEntity(Notification::ENTITY_TYPE);
|
||||
|
||||
$notification->set([
|
||||
'id' => Util::generateId(),
|
||||
'data' => [
|
||||
'noteId' => $note->getId(),
|
||||
],
|
||||
'type' => Notification::TYPE_NOTE,
|
||||
'userId' => $userId,
|
||||
'createdAt' => $now,
|
||||
'relatedId' => $note->getId(),
|
||||
'relatedType' => Note::ENTITY_TYPE,
|
||||
'relatedParentId' => $note->getParentId(),
|
||||
'relatedParentType' => $note->getParentType(),
|
||||
]);
|
||||
|
||||
$collection[] = $notification;
|
||||
}
|
||||
|
||||
if (!count($collection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->entityManager->getMapper()->massInsert($collection);
|
||||
|
||||
if ($this->config->get('useWebSocket')) {
|
||||
foreach ($userIdList as $userId) {
|
||||
$this->webSocketSubmission->submit('newNotification', $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkUserNoteAccess(User $user, Note $note): bool
|
||||
{
|
||||
if ($user->isPortal()) {
|
||||
if ($note->getRelatedType()) {
|
||||
/** @todo Revise. */
|
||||
return $note->getRelatedType() === Email::ENTITY_TYPE && $note->getParentType() === 'Case';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($note->getRelatedType()) {
|
||||
if (!$this->aclManager->checkScope($user, $note->getRelatedType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($note->getParentType()) {
|
||||
if (!$this->aclManager->checkScope($user, $note->getParentType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ use Espo\Core\Acl\Exceptions\NotImplemented as AclNotImplemented;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Services\Stream as Service;
|
||||
use Espo\Services\Notification as NotificationService;
|
||||
use Espo\Tools\Notification\Service as NotificationService;
|
||||
|
||||
/**
|
||||
* Handles auto-follow.
|
||||
|
||||
Reference in New Issue
Block a user