acl refactoring
This commit is contained in:
@@ -42,8 +42,6 @@ use Espo\Core\{
|
||||
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
|
||||
@@ -42,8 +42,6 @@ use Espo\Core\{
|
||||
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
|
||||
@@ -288,38 +288,6 @@ class Acl implements ScopeAcl, EntityAcl, EntityDeleteAcl
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOwnerUserIdAttribute(): ?string
|
||||
{
|
||||
if ($this->ownerUserIdAttribute) {
|
||||
return $this->ownerUserIdAttribute;
|
||||
}
|
||||
|
||||
if (!$this->scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$defs = $this->entityManager->getDefs()->getEntity($this->scope);
|
||||
|
||||
if (
|
||||
$defs->hasField(self::FIELD_ASSIGNED_USERS) &&
|
||||
$defs->getField(self::FIELD_ASSIGNED_USERS)->getType() === 'linkMultiple' &&
|
||||
$defs->hasRelation(self::FIELD_ASSIGNED_USERS) &&
|
||||
$defs->getRelation(self::FIELD_ASSIGNED_USERS)->getForeignEntityType() === 'User'
|
||||
) {
|
||||
return self::ATTR_ASSIGNED_USERS_IDS;
|
||||
}
|
||||
|
||||
if ($defs->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
return self::ATTR_ASSIGNED_USER_ID;
|
||||
}
|
||||
|
||||
if ($defs->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
return self::ATTR_CREATED_BY_ID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->config`.
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,7 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityAcl
|
||||
interface EntityAcl extends ScopeAcl
|
||||
{
|
||||
/**
|
||||
* Check access to an entity.
|
||||
@@ -44,10 +44,4 @@ interface EntityAcl
|
||||
* Get an access level for a specific action.
|
||||
*/
|
||||
public function getLevel(User $user, ScopeData $data, string $action) : string;
|
||||
|
||||
/**
|
||||
* Get an entity attribute that stores an ID (or IDs) of an owner-user.
|
||||
* NULL means no owner.
|
||||
*/
|
||||
public function getOwnerUserIdAttribute(): ?string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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\Core\Acl;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use Espo\ORM\Defs\Defs;
|
||||
|
||||
class OwnerUserFieldProvider
|
||||
{
|
||||
protected const FIELD_ASSIGNED_USERS = 'assignedUsers';
|
||||
|
||||
protected const FIELD_ASSIGNED_USER = 'assignedUser';
|
||||
|
||||
protected const FIELD_CREATED_BY = 'createdBy';
|
||||
|
||||
private $ormDefs;
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(Defs $ormDefs, Metadata $metadata)
|
||||
{
|
||||
$this->ormDefs = $ormDefs;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function get(string $entityType) : ?string
|
||||
{
|
||||
$value = $this->metadata->get(['entityAcl', $entityType, 'readOwnerUserField']);
|
||||
|
||||
if ($value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$defs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
if (
|
||||
$defs->hasField(self::FIELD_ASSIGNED_USERS) &&
|
||||
$defs->getField(self::FIELD_ASSIGNED_USERS)->getType() === 'linkMultiple' &&
|
||||
$defs->hasRelation(self::FIELD_ASSIGNED_USERS) &&
|
||||
$defs->getRelation(self::FIELD_ASSIGNED_USERS)->getForeignEntityType() === 'User'
|
||||
) {
|
||||
return self::FIELD_ASSIGNED_USERS;
|
||||
}
|
||||
|
||||
if (
|
||||
$defs->hasField(self::FIELD_ASSIGNED_USER) &&
|
||||
$defs->getField(self::FIELD_ASSIGNED_USER)->getType() === 'link' &&
|
||||
$defs->hasRelation(self::FIELD_ASSIGNED_USER) &&
|
||||
$defs->getRelation(self::FIELD_ASSIGNED_USER)->getForeignEntityType() === 'User'
|
||||
) {
|
||||
return self::FIELD_ASSIGNED_USER;
|
||||
}
|
||||
|
||||
if (
|
||||
$defs->hasField(self::FIELD_CREATED_BY) &&
|
||||
$defs->getField(self::FIELD_CREATED_BY)->getType() === 'link' &&
|
||||
$defs->hasRelation(self::FIELD_CREATED_BY) &&
|
||||
$defs->getRelation(self::FIELD_CREATED_BY)->getForeignEntityType() === 'User'
|
||||
) {
|
||||
return self::FIELD_CREATED_BY;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ use Espo\Core\{
|
||||
Acl\AclFactory,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Acl\GlobalRestricton,
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl as UserAclWrapper,
|
||||
Acl\Table as Table,
|
||||
Acl\ScopeAcl,
|
||||
@@ -83,15 +84,19 @@ class AclManager
|
||||
|
||||
protected $globalRestricton;
|
||||
|
||||
protected $ownerUserFieldProvider;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
EntityManager $entityManager,
|
||||
AclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory
|
||||
GlobalRestrictonFactory $globalRestrictonFactory,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
}
|
||||
@@ -635,4 +640,13 @@ class AclManager
|
||||
|
||||
return $this->globalRestricton->getScopeRestrictedLinkList($scope, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity field that stores an owner-user (or multiple users).
|
||||
* Must be link or linkMulitple field. NULL means no owner.
|
||||
*/
|
||||
public function getReadOwnerUserField(string $entityType): ?string
|
||||
{
|
||||
return $this->ownerUserFieldProvider->get($entityType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ use Espo\Core\{
|
||||
AclPortal\AclFactory as PortalAclFactory,
|
||||
Acl\ScopeAcl,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl\Table as TableBase,
|
||||
Portal\Acl as UserAclWrapper,
|
||||
AclManager as BaseAclManager,
|
||||
@@ -69,11 +70,13 @@ class AclManager extends BaseAclManager
|
||||
EntityManager $entityManager,
|
||||
PortalAclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider,
|
||||
BaseAclManager $mainManager
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
$this->mainManager = $mainManager;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
|
||||
@@ -50,11 +50,21 @@ class StreamNotesAcl
|
||||
|
||||
public function afterSave(Entity $entity, array $options = [])
|
||||
{
|
||||
if (!empty($options['noStream'])) return;
|
||||
if (!empty($options['silent'])) return;
|
||||
if (!empty($options['skipStreamNotesAcl'])) return;
|
||||
if (!empty($options['noStream'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isNew()) return;
|
||||
if (!empty($options['silent'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($options['skipStreamNotesAcl'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->noteService) {
|
||||
$this->noteService = $this->serviceFactory->create('Note');
|
||||
|
||||
@@ -40,6 +40,8 @@ use Espo\Core\{
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use StdClass;
|
||||
|
||||
class Notifications
|
||||
{
|
||||
protected $notificationService = null;
|
||||
@@ -49,9 +51,13 @@ class Notifications
|
||||
public static $order = 14;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $serviceFactory;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $internalAclManager;
|
||||
|
||||
public function __construct(
|
||||
@@ -72,12 +78,15 @@ class Notifications
|
||||
{
|
||||
$mentionedUserList = [];
|
||||
$data = $entity->get('data');
|
||||
if (($data instanceof \StdClass) && ($data->mentions instanceof \StdClass)) {
|
||||
|
||||
if (($data instanceof StdClass) && ($data->mentions instanceof StdClass)) {
|
||||
$mentions = get_object_vars($data->mentions);
|
||||
|
||||
foreach ($mentions as $d) {
|
||||
$mentionedUserList[] = $d->id;
|
||||
}
|
||||
}
|
||||
|
||||
return $mentionedUserList;
|
||||
}
|
||||
|
||||
@@ -100,16 +109,27 @@ class Notifications
|
||||
$notifyUserIdList = [];
|
||||
|
||||
if ($parentType && $parentId) {
|
||||
$userList = $this->getSubscriberList($parentType, $parentId, $entity->get('isInternal'));
|
||||
$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'));
|
||||
$additionalUserList = $this
|
||||
->getSubscriberList($superParentType, $superParentId, $entity->get('isInternal'));
|
||||
|
||||
foreach ($additionalUserList as $user) {
|
||||
if ($user->isPortal()) continue;
|
||||
if (in_array($user->id, $userIdMetList)) continue;
|
||||
if ($user->isPortal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($user->id, $userIdMetList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userIdMetList[] = $user->id;
|
||||
$userList[] = $user;
|
||||
}
|
||||
@@ -117,14 +137,17 @@ class Notifications
|
||||
|
||||
if ($entity->get('relatedType')) {
|
||||
$targetType = $entity->get('relatedType');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$targetType = $parentType;
|
||||
}
|
||||
|
||||
$skipAclCheck = false;
|
||||
|
||||
if (!$entity->isAclProcessed()) {
|
||||
$skipAclCheck = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
$userIdList = $entity->getLinkMultipleIdList('users');
|
||||
}
|
||||
@@ -132,19 +155,24 @@ class Notifications
|
||||
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 {
|
||||
}
|
||||
else {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -152,26 +180,34 @@ class Notifications
|
||||
|
||||
if ($level === 'all') {
|
||||
$notifyUserIdList[] = $user->id;
|
||||
|
||||
continue;
|
||||
} else if ($level === 'team') {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -179,60 +215,104 @@ class Notifications
|
||||
|
||||
} 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;
|
||||
if ($userId === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($userId, $notifyUserIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $userId;
|
||||
}
|
||||
}
|
||||
} else if ($targetType === 'teams') {
|
||||
}
|
||||
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->getRepository('Team')->findRelated($team, 'users', array(
|
||||
'whereClause' => array(
|
||||
'isActive' => true
|
||||
)
|
||||
));
|
||||
|
||||
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;
|
||||
if ($user->id === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($user->id, $notifyUserIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($targetType === 'portals') {
|
||||
}
|
||||
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->getRepository('Portal')->findRelated($portal, 'users', array(
|
||||
'whereClause' => array(
|
||||
'isActive' => true
|
||||
)
|
||||
));
|
||||
|
||||
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;
|
||||
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')->find([
|
||||
'whereClause' => [
|
||||
}
|
||||
else if ($targetType === 'all') {
|
||||
$targetUserList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->where([
|
||||
'isActive' => true,
|
||||
'type' => ['regular', 'admin']
|
||||
]
|
||||
]);
|
||||
'type' => ['regular', 'admin'],
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->id === $this->user->id) continue;
|
||||
if ($user->id === $this->user->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifyUserIdList[] = $user->id;
|
||||
}
|
||||
}
|
||||
@@ -243,18 +323,25 @@ class Notifications
|
||||
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()
|
||||
$this->entityManager
|
||||
->getRepository('Notification')
|
||||
->select(['id'])
|
||||
->where([
|
||||
'type' => 'Note',
|
||||
'relatedType' => 'Note',
|
||||
'relatedId' => $entity->id,
|
||||
'userId' => $userId,
|
||||
])
|
||||
->findOne()
|
||||
) {
|
||||
unset($notifyUserIdList[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ use Espo\Core\{
|
||||
|
||||
class Meeting extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"readOwnerUserField": "users"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"readOwnerUserField": "users"
|
||||
}
|
||||
@@ -8,5 +8,6 @@
|
||||
"users": {
|
||||
"onlyAdmin": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"readOwnerUserField": "users"
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ use Espo\Entities\Note as NoteEntity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use StdClass;
|
||||
use Exception;
|
||||
|
||||
class Note extends Record
|
||||
{
|
||||
@@ -104,9 +104,9 @@ class Note extends Record
|
||||
$parentId = $data->parentId ?? null;
|
||||
|
||||
if ($parentType && $parentId) {
|
||||
$entity = $this->entityManager->getEntity($data->parentType, $data->parentId);
|
||||
$parent = $this->entityManager->getEntity($data->parentType, $data->parentId);
|
||||
|
||||
if ($entity && !$this->acl->check($entity, AclTable::ACTION_READ)) {
|
||||
if ($parent && !$this->acl->check($parent, AclTable::ACTION_READ)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
@@ -372,6 +372,9 @@ class Note extends Record
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@@ -388,33 +391,45 @@ class Note extends Record
|
||||
return;
|
||||
}
|
||||
|
||||
$ownerUserIdAttribute = $this->getAclManager()
|
||||
->getImplementation($entityType)
|
||||
->getOwnerUserIdAttribute();
|
||||
|
||||
$usersAttributeIsChanged = false;
|
||||
$teamsAttributeIsChanged = false;
|
||||
|
||||
if ($ownerUserIdAttribute) {
|
||||
$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 ($entity->getAttributeParam($ownerUserIdAttribute, 'isLinkMultipleIdList')) {
|
||||
$userLink = $entity->getAttributeParam($ownerUserIdAttribute, 'relation');
|
||||
|
||||
$userIdList = $entity->getLinkMultipleIdList($userLink);
|
||||
if ($fieldDefs->getType() === 'linkMultiple') {
|
||||
$userIdList = $entity->getLinkMultipleIdList($ownerUserField);
|
||||
}
|
||||
else {
|
||||
$userId = $entity->get($ownerUserIdAttribute);
|
||||
|
||||
if ($userId) {
|
||||
$userIdList = [$userId];
|
||||
}
|
||||
else {
|
||||
$userIdList = [];
|
||||
}
|
||||
$userIdList = $userId ? [$userId] : [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,70 +444,98 @@ class Note extends Record
|
||||
}
|
||||
}
|
||||
|
||||
if ($usersAttributeIsChanged || $teamsAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$noteList = $this->entityManager
|
||||
->getRepository('Note')
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType
|
||||
],
|
||||
[
|
||||
'parentId' => $entity->id,
|
||||
'parentType' => $entityType,
|
||||
'superParentId!=' => null,
|
||||
'relatedId' => null,
|
||||
]
|
||||
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();
|
||||
]
|
||||
])
|
||||
->select([
|
||||
'id',
|
||||
'parentType',
|
||||
'parentId',
|
||||
'superParentType',
|
||||
'superParentId',
|
||||
'isInternal',
|
||||
'relatedType',
|
||||
'relatedId',
|
||||
'createdAt',
|
||||
])
|
||||
->find();
|
||||
|
||||
$noteOptions = [];
|
||||
$noteOptions = [];
|
||||
|
||||
if (!empty($forceProcessNoteNotifications)) {
|
||||
$noteOptions['forceProcessNotifications'] = true;
|
||||
}
|
||||
if (!empty($forceProcessNoteNotifications)) {
|
||||
$noteOptions['forceProcessNotifications'] = true;
|
||||
}
|
||||
|
||||
$period = '-' . $this->getConfig()->get('noteNotificationPeriod', $this->noteNotificationPeriod);
|
||||
$period = '-' . $this->getConfig()->get('noteNotificationPeriod', $this->noteNotificationPeriod);
|
||||
|
||||
$threshold = new DateTime();
|
||||
$threshold = (new DateTime())->modify($period);
|
||||
|
||||
$threshold->modify($period);
|
||||
|
||||
foreach ($noteList as $note) {
|
||||
if (!$entity->isNew()) {
|
||||
try {
|
||||
$createdAtDt = new DateTime($note->get('createdAt'));
|
||||
|
||||
if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {};
|
||||
}
|
||||
|
||||
if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$note->set('teamsIds', $teamIdList);
|
||||
}
|
||||
|
||||
if ($usersAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$note->set('usersIds', $userIdList);
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($note, $noteOptions);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ class Stream
|
||||
|
||||
if (!$user) {
|
||||
unset($userIdList[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -223,7 +224,8 @@ class Stream
|
||||
$userId = $this->user->id;
|
||||
}
|
||||
|
||||
$isFollowed = (bool) $this->entityManager->getRepository('Subscription')
|
||||
$isFollowed = (bool) $this->entityManager
|
||||
->getRepository('Subscription')
|
||||
->select(['id'])
|
||||
->where([
|
||||
'userId' => $userId,
|
||||
@@ -1223,21 +1225,49 @@ class Stream
|
||||
}
|
||||
}
|
||||
|
||||
$ownerUserIdAttribute = $this->aclManager
|
||||
->getImplementation($entity->getEntityType())
|
||||
->getOwnerUserIdAttribute();
|
||||
$ownerUserField = $this->aclManager->getReadOwnerUserField($entity->getEntityType());
|
||||
|
||||
if ($ownerUserIdAttribute && $entity->get($ownerUserIdAttribute)) {
|
||||
if ($entity->getAttributeParam($ownerUserIdAttribute, 'isLinkMultipleIdList')) {
|
||||
$userIdList = $entity->get($ownerUserIdAttribute);
|
||||
}
|
||||
else {
|
||||
$userId = $entity->get($ownerUserIdAttribute);
|
||||
$userIdList = [$userId];
|
||||
}
|
||||
|
||||
$note->set('usersIds', $userIdList);
|
||||
if (!$ownerUserField) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* @var $defs \Espo\ORM\Defs\EntityDefs */
|
||||
$defs = $this->entityManager->getDefs()->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$defs->hasField($ownerUserField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldDefs = $defs->getField($ownerUserField);
|
||||
|
||||
if ($fieldDefs->getType() === 'linkMultiple') {
|
||||
$ownerUserIdAttribute = $ownerUserField . 'Ids';
|
||||
}
|
||||
else if ($fieldDefs->getType() === 'link') {
|
||||
$ownerUserIdAttribute = $ownerUserField . 'Id';
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->has($ownerUserIdAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($fieldDefs->getType() === 'linkMultiple') {
|
||||
$userIdList = $entity->getLinkMultipleIdList($ownerUserField);
|
||||
}
|
||||
else {
|
||||
$userId = $entity->get($ownerUserIdAttribute);
|
||||
|
||||
if (!$userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userIdList = [$userId];
|
||||
}
|
||||
|
||||
$note->set('usersIds', $userIdList);
|
||||
}
|
||||
|
||||
public function noteEmailReceived(Entity $entity, Entity $email, $isInitial = false): void
|
||||
@@ -1379,6 +1409,7 @@ class Stream
|
||||
$note->set('superParentId', $entity->get('accountId'));
|
||||
$note->set('superParentType', 'Account');
|
||||
|
||||
// only if has super parent
|
||||
$this->processNoteTeamsUsers($note, $entity);
|
||||
}
|
||||
|
||||
@@ -1527,6 +1558,7 @@ class Stream
|
||||
$note->set('superParentId', $entity->get('accountId'));
|
||||
$note->set('superParentType', 'Account');
|
||||
|
||||
// only if has super parent
|
||||
$this->processNoteTeamsUsers($note, $entity);
|
||||
}
|
||||
|
||||
@@ -1839,7 +1871,7 @@ class Stream
|
||||
}
|
||||
|
||||
if (
|
||||
$this->aclManager->getLevel($user, $scope, 'read') === 'team'
|
||||
$this->aclManager->checkReadOnlyTeam($user, $scope)
|
||||
) {
|
||||
$list[] = $scope;
|
||||
}
|
||||
@@ -1872,7 +1904,7 @@ class Stream
|
||||
}
|
||||
|
||||
if (
|
||||
$this->aclManager->getLevel($user, $scope, 'read') === 'own'
|
||||
$this->aclManager->checkReadOnlyOwn($user, $scope)
|
||||
) {
|
||||
$list[] = $scope;
|
||||
}
|
||||
|
||||
@@ -35,24 +35,29 @@ use Espo\Core\{
|
||||
|
||||
class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
public function testGetOwnerUserIdAttribute()
|
||||
public function testGetReadOwnerUserField()
|
||||
{
|
||||
/* @var $aclManager AclManager */
|
||||
$aclManager = $this->getContainer()->get('aclManager');
|
||||
|
||||
$this->assertEquals(
|
||||
'assignedUserId',
|
||||
$aclManager->getImplementation('Account')->getOwnerUserIdAttribute()
|
||||
'assignedUser',
|
||||
$aclManager->getReadOwnerUserField('Account')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'createdById',
|
||||
$aclManager->getImplementation('Import')->getOwnerUserIdAttribute()
|
||||
'createdBy',
|
||||
$aclManager->getReadOwnerUserField('Import')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'usersIds',
|
||||
$aclManager->getImplementation('Email')->getOwnerUserIdAttribute()
|
||||
'users',
|
||||
$aclManager->getReadOwnerUserField('Email')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'users',
|
||||
$aclManager->getReadOwnerUserField('Meeting')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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 tests\integration\Espo\Note;
|
||||
|
||||
use Espo\{
|
||||
Services\Stream as StreamService,
|
||||
Services\Note as NoteService,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
public function testProcessNoteAcl()
|
||||
{
|
||||
/* @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');
|
||||
|
||||
$team1 = $em->createEntity('Team', [
|
||||
'name' => 'team-1',
|
||||
]);
|
||||
|
||||
$team2 = $em->createEntity('Team', [
|
||||
'name' => 'team-2',
|
||||
]);
|
||||
|
||||
$user1 = $em->createEntity('User', [
|
||||
'userName' => 'user-1',
|
||||
'lastName' => 'user-1',
|
||||
]);
|
||||
|
||||
$user2 = $em->createEntity('User', [
|
||||
'userName' => 'user-2',
|
||||
'lastName' => 'user-2',
|
||||
]);
|
||||
|
||||
$account = $em->createEntity('Account', [
|
||||
|
||||
]);
|
||||
|
||||
// Opportunity
|
||||
|
||||
$opportunity = $em->createEntity('Opportunity', [
|
||||
'assignedUserId' => $user1->getId(),
|
||||
'teamsIds' => [$team1->getId()],
|
||||
'accountId' => $account->getId(),
|
||||
]);
|
||||
|
||||
$streamService->noteCreate($opportunity);
|
||||
|
||||
$note1 = $em
|
||||
->getRDBRepository('Note')
|
||||
->where([
|
||||
'type' => 'Create',
|
||||
'parentId' => $opportunity->getId(),
|
||||
'parentType' => $opportunity->getEntityType(),
|
||||
])
|
||||
->findOne();
|
||||
|
||||
$this->assertEquals([$team1->getId()], $note1->getLinkMultipleIdList('teams'));
|
||||
$this->assertEquals([$user1->getId()], $note1->getLinkMultipleIdList('users'));
|
||||
|
||||
$opportunity->set([
|
||||
'assignedUserId' => $user2->getId(),
|
||||
'teamsIds' => [$team2->getId()],
|
||||
]);
|
||||
|
||||
$em->saveEntity($opportunity);
|
||||
|
||||
$note1 = $em->getEntity('Note', $note1->getId());
|
||||
|
||||
$this->assertEquals([$team2->getId()], $note1->getLinkMultipleIdList('teams'));
|
||||
$this->assertEquals([$user2->getId()], $note1->getLinkMultipleIdList('users'));
|
||||
|
||||
// Meeting
|
||||
|
||||
$meeting = $em->createEntity('Meeting', [
|
||||
'usersIds' => [$user1->getId()],
|
||||
'teamsIds' => [$team1->getId()],
|
||||
'parentId' => $account->getId(),
|
||||
'parentType' => $account->getEntityType(),
|
||||
]);
|
||||
|
||||
$streamService->noteRelate($meeting, $account->getEntityType(), $account->getId());
|
||||
|
||||
$note2 = $em
|
||||
->getRDBRepository('Note')
|
||||
->where([
|
||||
'type' => 'Relate',
|
||||
'relatedId' => $meeting->getId(),
|
||||
'relatedType' => $meeting->getEntityType(),
|
||||
])
|
||||
->findOne();
|
||||
|
||||
$this->assertEquals([$team1->getId()], $note2->getLinkMultipleIdList('teams'));
|
||||
$this->assertEquals([$user1->getId()], $note2->getLinkMultipleIdList('users'));
|
||||
|
||||
$meeting->set([
|
||||
'usersIds' => [$user2->getId()],
|
||||
'teamsIds' => [$team2->getId()],
|
||||
]);
|
||||
|
||||
$em->saveEntity($meeting);
|
||||
|
||||
$note2 = $em->getEntity('Note', $note2->getId());
|
||||
|
||||
$this->assertEquals([$team2->getId()], $note2->getLinkMultipleIdList('teams'));
|
||||
$this->assertEquals([$user2->getId()], $note2->getLinkMultipleIdList('users'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user