acl refactoring
This commit is contained in:
@@ -39,6 +39,6 @@ class ActionHistoryRecord extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
return $entity->get('userId') === $user->id;
|
||||
return $entity->get('userId') === $user->getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,23 @@
|
||||
|
||||
namespace Espo\Acl;
|
||||
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\Entities\{
|
||||
User as EntityUser,
|
||||
Note,
|
||||
};
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Attachment extends Acl
|
||||
class Attachment extends Acl implements EntityReadAcl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
@@ -62,12 +70,8 @@ class Attachment extends Acl
|
||||
$parent = $this->entityManager->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
|
||||
}
|
||||
|
||||
if (!$hasParent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$parent) {
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if (!$parent || !$hasParent) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,31 +79,67 @@ class Attachment extends Acl
|
||||
}
|
||||
|
||||
if ($parent->getEntityType() === 'Note') {
|
||||
if (!$parent->get('parentId') || !$parent->get('parentType')) {
|
||||
return true;
|
||||
}
|
||||
$result = $this->checkEntityReadNoteParent($user, $parent);
|
||||
|
||||
$parentOfParent = $this->entityManager
|
||||
->getEntity($parent->get('parentType'), $parent->get('parentId'));
|
||||
|
||||
if ($parentOfParent && $this->aclManager->checkEntity($user, $parentOfParent)) {
|
||||
return true;
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
else if ($this->aclManager->checkEntity($user, $parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkEntityReadNoteParent(EntityUser $user, Note $note): ?bool
|
||||
{
|
||||
if ($note->getTargetType() === Note::TARGET_TEAMS) {
|
||||
$intersect = array_intersect(
|
||||
$note->getLinkMultipleIdList('teams'),
|
||||
$user->getLinkMultipleIdList('teams')
|
||||
);
|
||||
|
||||
if (count($intersect)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($note->getTargetType() === Note::TARGET_USERS) {
|
||||
$isRelated = $this->entityManager
|
||||
->getRDBRepository('Note')
|
||||
->getRelation($note, 'users')
|
||||
->isRelated($user);
|
||||
|
||||
if ($isRelated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$note->getParentId() || !$note->getParentType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntity($note->getParentType(), $note->getParentId());
|
||||
|
||||
if ($parent && $this->aclManager->checkEntity($user, $parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->getId() === $entity->get('createdById')) {
|
||||
if ($user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,23 +33,28 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Email extends Acl
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if (!$data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->read === false || $data->read === 'no') {
|
||||
if ($data->getRead() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,13 +88,13 @@ class Email extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if (!$data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -114,11 +119,11 @@ class Email extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->checkEntity($user, $entity, $data, 'delete')) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($data->edit === 'no' && $data->create === 'no') {
|
||||
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,30 +33,34 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Import extends Acl
|
||||
class Import extends Acl implements EntityReadAcl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($user->id === $entity->get('createdById')) {
|
||||
if ($user->getId() === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($user->id === $entity->get('createdById')) {
|
||||
if ($user->getId() === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,17 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
use Exception;
|
||||
use DateTime;
|
||||
|
||||
class Note extends Acl
|
||||
class Note extends Acl implements EntityEditAcl
|
||||
{
|
||||
protected $deleteThresholdPeriod = '1 month';
|
||||
|
||||
@@ -53,7 +58,7 @@ class Note extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$entity->get('parentId') || !$entity->get('parentType')) {
|
||||
return true;
|
||||
@@ -68,13 +73,13 @@ class Note extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->checkEntity($user, $entity, $data, 'edit')) {
|
||||
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_EDIT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -107,13 +112,13 @@ class Note extends Acl
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->checkEntity($user, $entity, $data, 'delete')) {
|
||||
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Notification extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->id === $entity->get('userId')) {
|
||||
if ($user->getId() === $entity->get('userId')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,43 +33,50 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class ScheduledJob extends Acl
|
||||
class ScheduledJob extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('isInternal')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'read');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('isInternal')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'edit');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('isInternal')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'delete');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_DELETE);
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('isInternal')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'create');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ class Team extends Acl
|
||||
{
|
||||
$userTeamIdList = $user->getLinkMultipleIdList('teams');
|
||||
|
||||
return in_array($entity->id, $userTeamIdList);
|
||||
return in_array($entity->getId(), $userTeamIdList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,27 +33,34 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class User extends Acl
|
||||
class User extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
return $user->id === $entity->id;
|
||||
return $user->getId() === $entity->getId();
|
||||
}
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$user->isAdmin() && $entity->isPortal()) {
|
||||
if ($this->getAclManager()->get($user, 'portalPermission') === 'yes') {
|
||||
if ($this->getAclManager()->get($user, 'portal') === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'read');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$user->isAdmin()) {
|
||||
return false;
|
||||
@@ -63,12 +70,12 @@ class User extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'create');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->id === 'system') {
|
||||
if ($entity->getId() === 'system') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -87,7 +94,7 @@ class User extends Acl
|
||||
return parent::checkEntityDelete($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->id === 'system') {
|
||||
return false;
|
||||
@@ -97,7 +104,7 @@ class User extends Acl
|
||||
}
|
||||
|
||||
if (!$user->isAdmin()) {
|
||||
if ($user->id !== $entity->id) {
|
||||
if ($user->getId() !== $entity->getId()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +113,6 @@ class User extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkEntity($user, $entity, $data, 'edit');
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,22 +33,28 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class Webhook extends Acl
|
||||
class Webhook extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
return $user->id === $entity->get('userId') && $user->isApi();
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,13 +65,13 @@ class Webhook extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -76,13 +82,13 @@ class Webhook extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,13 +99,13 @@ class Webhook extends Acl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,65 +29,113 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\Entities\Note;
|
||||
|
||||
class Attachment extends \Espo\Core\AclPortal\Base
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Attachment extends Acl implements EntityReadAcl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($entity->get('parentType') === 'Settings') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parent = null;
|
||||
$hasParent = false;
|
||||
|
||||
if ($entity->get('parentId') && $entity->get('parentType')) {
|
||||
$hasParent = true;
|
||||
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
} else if ($entity->get('relatedId') && $entity->get('relatedType')) {
|
||||
|
||||
$parent = $this->entityManager->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
}
|
||||
else if ($entity->get('relatedId') && $entity->get('relatedType')) {
|
||||
$hasParent = true;
|
||||
$parent = $this->getEntityManager()->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
|
||||
|
||||
$parent = $this->entityManager->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
|
||||
}
|
||||
|
||||
if ($hasParent) {
|
||||
if ($parent) {
|
||||
if ($parent->getEntityType() === 'Note') {
|
||||
if ($parent->get('parentId') && $parent->get('parentType')) {
|
||||
$parentOfParent = $this->getEntityManager()->getEntity($parent->get('parentType'), $parent->get('parentId'));
|
||||
if ($parentOfParent && $this->getAclManager()->checkEntity($user, $parentOfParent)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($this->getAclManager()->checkEntity($user, $parent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!$hasParent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($parent->getEntityType() === 'Note') {
|
||||
$result = $this->checkEntityReadNoteParent($user, $parent);
|
||||
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else if ($this->aclManager->checkEntity($user, $parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkEntityReadNoteParent(EntityUser $user, Note $note): ?bool
|
||||
{
|
||||
if ($note->isInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($note->getTargetType() === Note::TARGET_PORTALS) {
|
||||
$intersect = array_intersect(
|
||||
$note->getLinkMultipleIdList('portals'),
|
||||
$user->getLinkMultipleIdList('portals')
|
||||
);
|
||||
|
||||
if (count($intersect)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($note->getTargetType() === Note::TARGET_USERS) {
|
||||
$isRelated = $this->entityManager
|
||||
->getRDBRepository('Note')
|
||||
->getRelation($note, 'users')
|
||||
->isRelated($user);
|
||||
|
||||
if ($isRelated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$note->getParentId() || !$note->getParentType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntity($note->getParentType(), $note->getParentId());
|
||||
|
||||
if ($parent && $this->aclManager->checkEntity($user, $parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->id === $entity->get('createdById')) {
|
||||
if ($user->getId() === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,43 +29,54 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
|
||||
class Email extends \Espo\Core\AclPortal\Base
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($data === false) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
if (is_object($data)) {
|
||||
if ($data->read === false || $data->read === 'no') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->getRead() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$entity->has('usersIds')) {
|
||||
$entity->loadLinkMultipleField('users');
|
||||
}
|
||||
|
||||
$userIdList = $entity->get('usersIds');
|
||||
if (is_array($userIdList) && in_array($user->id, $userIdList)) {
|
||||
|
||||
if (is_array($userIdList) && in_array($user->getId(), $userIdList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->id === $entity->get('createdById')) {
|
||||
if ($user->getId() === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,19 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class EmailAddress extends \Espo\Core\AclPortal\Base
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class EmailAddress extends Acl
|
||||
{
|
||||
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
|
||||
{
|
||||
$id = $entity->id;
|
||||
$id = $entity->getId();
|
||||
|
||||
$isFobidden = false;
|
||||
|
||||
@@ -46,13 +51,14 @@ class EmailAddress extends \Espo\Core\AclPortal\Base
|
||||
$entityWithSameAddressList = $repository->getEntityListByAddressId($id, $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameAddressList as $e) {
|
||||
if (!$this->getAclManager()->check($user, $e, 'edit')) {
|
||||
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
$isFobidden = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$isFobidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,22 +29,32 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Note extends \Espo\Core\AclPortal\Base
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Note extends Acl implements EntityCreateAcl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($entity->get('type') === 'Post' && $user->id === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('type') !== 'Post') return false;
|
||||
if ($entity->get('type') !== 'Post') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entity->get('type') === 'Post' && $entity->get('targetType')) {
|
||||
return false;
|
||||
@@ -55,8 +65,9 @@ class Note extends \Espo\Core\AclPortal\Base
|
||||
}
|
||||
|
||||
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
|
||||
if ($parent) {
|
||||
if ($this->getAclManager()->checkEntity($user, $parent, 'stream')) {
|
||||
if ($this->getAclManager()->checkEntity($user, $parent, Table::ACTION_STREAM)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,17 +29,21 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Notification extends \Espo\Core\AclPortal\Base
|
||||
use Espo\Core\{
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Notification extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->id === $entity->get('userId')) {
|
||||
if ($user->getId() === $entity->get('userId')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,14 +29,19 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\Entities\User as EntityUser;
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\Entities\User as EntityUser;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class PhoneNumber extends \Espo\Core\AclPortal\Base
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class PhoneNumber extends Acl
|
||||
{
|
||||
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
|
||||
{
|
||||
$id = $entity->id;
|
||||
$id = $entity->getId();
|
||||
|
||||
$isFobidden = false;
|
||||
|
||||
@@ -46,7 +51,7 @@ class PhoneNumber extends \Espo\Core\AclPortal\Base
|
||||
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($id, $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameNumberList as $e) {
|
||||
if (!$this->getAclManager()->check($user, $e, 'edit')) {
|
||||
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
$isFobidden = true;
|
||||
|
||||
break;
|
||||
|
||||
@@ -29,13 +29,17 @@
|
||||
|
||||
namespace Espo\AclPortal;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Entities\User as UserEntity;
|
||||
|
||||
class User extends \Espo\Core\AclPortal\Base
|
||||
use Espo\Core\{
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class User extends Acl
|
||||
{
|
||||
public function checkIsOwner(\Espo\Entities\User $user, Entity $entity)
|
||||
public function checkIsOwner(UserEntity $user, Entity $entity)
|
||||
{
|
||||
return $user->id === $entity->id;
|
||||
return $user->getId() === $entity->getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class Acl
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getMap() : StdClass
|
||||
public function getMap(): StdClass
|
||||
{
|
||||
return $this->aclManager->getMap($this->user);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class Acl
|
||||
/**
|
||||
* Get an access level for a specific scope and action.
|
||||
*/
|
||||
public function getLevel(string $scope, string $action) : string
|
||||
public function getLevel(string $scope, string $action): string
|
||||
{
|
||||
return $this->aclManager->getLevel($this->user, $scope, $action);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class Acl
|
||||
/**
|
||||
* Get a permission. E.g. 'assignment' permission.
|
||||
*/
|
||||
public function get(string $permission) : ?string
|
||||
public function get(string $permission): ?string
|
||||
{
|
||||
return $this->aclManager->get($this->user, $permission);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ class Acl
|
||||
/**
|
||||
* Whether there's no 'read' access for a specific scope.
|
||||
*/
|
||||
public function checkReadNo(string $scope) : bool
|
||||
public function checkReadNo(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadNo($this->user, $scope);
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class Acl
|
||||
/**
|
||||
* Whether 'read' access is set to 'team' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyTeam(string $scope) : bool
|
||||
public function checkReadOnlyTeam(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyTeam($this->user, $scope);
|
||||
}
|
||||
@@ -93,7 +93,7 @@ class Acl
|
||||
/**
|
||||
* Whether 'read' access is set to 'own' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyOwn(string $scope) : bool
|
||||
public function checkReadOnlyOwn(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyOwn($this->user, $scope);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class Acl
|
||||
/**
|
||||
* Check a scope or entity. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
*/
|
||||
public function check($subject, ?string $action = null) : bool
|
||||
public function check($subject, ?string $action = null): bool
|
||||
{
|
||||
return $this->aclManager->check($this->user, $subject, $action);
|
||||
}
|
||||
@@ -109,23 +109,63 @@ class Acl
|
||||
/**
|
||||
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
*/
|
||||
public function checkScope(string $scope, ?string $action = null) : bool
|
||||
public function checkScope(string $scope, ?string $action = null): bool
|
||||
{
|
||||
return $this->aclManager->checkScope($this->user, $scope, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to a specific entity (record).
|
||||
* Check access to a specific entity.
|
||||
*/
|
||||
public function checkEntity(Entity $entity, string $action = Table::ACTION_READ) : bool
|
||||
public function checkEntity(Entity $entity, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
return $this->aclManager->checkEntity($this->user, $entity, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'read' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityRead(Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'create' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityCreate(Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'edit' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityEdit(Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'delete' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityDelete(Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'stream' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityStream(Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkUser(string $permission, User $entity) : bool
|
||||
public function checkUser(string $permission, User $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkUser($this->user, $permission, $entity);
|
||||
}
|
||||
@@ -133,7 +173,7 @@ class Acl
|
||||
/**
|
||||
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
|
||||
*/
|
||||
public function checkIsOwner(Entity $entity) : bool
|
||||
public function checkIsOwner(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkIsOwner($this->user, $entity);
|
||||
}
|
||||
@@ -141,7 +181,7 @@ class Acl
|
||||
/**
|
||||
* Whether a user team list overlaps with teams set in an entity.
|
||||
*/
|
||||
public function checkInTeam(Entity $entity) : bool
|
||||
public function checkInTeam(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkInTeam($this->user, $entity);
|
||||
}
|
||||
@@ -150,8 +190,10 @@ class Acl
|
||||
* Get attributes forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
return $this->aclManager
|
||||
->getScopeForbiddenAttributeList($this->user, $scope, $action, $thresholdLevel);
|
||||
@@ -161,8 +203,10 @@ class Acl
|
||||
* Get fields forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
return $this->aclManager
|
||||
->getScopeForbiddenFieldList($this->user, $scope, $action, $thresholdLevel);
|
||||
@@ -172,8 +216,10 @@ class Acl
|
||||
* Get links forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
return $this->aclManager->getScopeForbiddenLinkList($this->user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
@@ -183,7 +229,7 @@ class Acl
|
||||
*
|
||||
* @param User|string $target User entity or user ID.
|
||||
*/
|
||||
public function checkUserPermission($target, string $permissionType = 'user') : bool
|
||||
public function checkUserPermission($target, string $permissionType = 'user'): bool
|
||||
{
|
||||
return $this->aclManager->checkUserPermission($this->user, $target, $permissionType);
|
||||
}
|
||||
@@ -193,22 +239,22 @@ class Acl
|
||||
*
|
||||
* @param User|string $target User entity or user ID.
|
||||
*/
|
||||
public function checkAssignmentPermission($target) : bool
|
||||
public function checkAssignmentPermission($target): bool
|
||||
{
|
||||
return $this->aclManager->checkAssignmentPermission($this->user, $target);
|
||||
}
|
||||
|
||||
public function getScopeRestrictedFieldList(string $scope, $type) : array
|
||||
public function getScopeRestrictedFieldList(string $scope, $type): array
|
||||
{
|
||||
return $this->aclManager->getScopeRestrictedFieldList($scope, $type);
|
||||
}
|
||||
|
||||
public function getScopeRestrictedAttributeList(string $scope, $type) : array
|
||||
public function getScopeRestrictedAttributeList(string $scope, $type): array
|
||||
{
|
||||
return $this->aclManager->getScopeRestrictedAttributeList($scope, $type);
|
||||
}
|
||||
|
||||
public function getScopeRestrictedLinkList(string $scope, $type) : array
|
||||
public function getScopeRestrictedLinkList(string $scope, $type): array
|
||||
{
|
||||
return $this->aclManager->getScopeRestrictedLinkList($scope, $type);
|
||||
}
|
||||
|
||||
+116
-102
@@ -43,99 +43,83 @@ use Espo\Core\{
|
||||
/**
|
||||
* An implementation for access checking for entities. Can be overridden in `Acl` namespace.
|
||||
*/
|
||||
class Acl implements ScopeAcl, EntityAcl
|
||||
class Acl implements ScopeAcl, EntityAcl, EntityDeleteAcl
|
||||
{
|
||||
protected const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
protected const ATTR_ASSIGNED_USER_ID = 'assignedUserId';
|
||||
|
||||
protected const ATTR_ASSIGNED_USERS_IDS = 'assignedUsersIds';
|
||||
|
||||
protected const ATTR_ASSIGNED_TEAMS_IDS = 'teamsIds';
|
||||
|
||||
protected const FIELD_TEAMS = 'teams';
|
||||
|
||||
protected const FIELD_ASSIGNED_USERS = 'assignedUsers';
|
||||
|
||||
protected $scope;
|
||||
|
||||
protected $ownerUserIdAttribute = null;
|
||||
|
||||
protected $allowDeleteCreatedThresholdPeriod = '24 hours';
|
||||
|
||||
protected $entityManager;
|
||||
protected $checkIsOwnerentityManager;
|
||||
|
||||
protected $aclManager;
|
||||
|
||||
protected $config;
|
||||
|
||||
public function __construct(string $scope, EntityManager $entityManager, AclManager $aclManager, Config $config)
|
||||
{
|
||||
$this->scope = $scope;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
AclManager $aclManager,
|
||||
Config $config,
|
||||
string $scope = null
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
return $this->config;
|
||||
return $this->checkScopeInternal($user, $data, $action, $entity);
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null) : bool
|
||||
{
|
||||
return $this->entityManager;
|
||||
return $this->checkScopeInternal($user, $data, $action);
|
||||
}
|
||||
|
||||
protected function getAclManager()
|
||||
public function checkReadOnlyTeam(User $user, ScopeData $data) : bool
|
||||
{
|
||||
return $this->aclManager;
|
||||
return $data->getRead() === Table::LEVEL_TEAM;
|
||||
}
|
||||
|
||||
public function checkReadOnlyTeam(User $user, $data)
|
||||
public function checkReadNo(User $user, ScopeData $data) : bool
|
||||
{
|
||||
if (empty($data) || !is_object($data) || !isset($data->read)) {
|
||||
return $data->getRead() === Table::LEVEL_NO;
|
||||
}
|
||||
|
||||
public function checkReadOnlyOwn(User $user, ScopeData $data) : bool
|
||||
{
|
||||
return $data->getRead() === Table::LEVEL_OWN;
|
||||
}
|
||||
|
||||
protected function checkScopeInternal(
|
||||
User $user,
|
||||
ScopeData $data,
|
||||
?string $action = null,
|
||||
?Entity $entity = null,
|
||||
array $entityAccessData = []
|
||||
) : bool {
|
||||
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data->read === Table::LEVEL_TEAM;
|
||||
}
|
||||
|
||||
public function checkReadNo(User $user, $data)
|
||||
{
|
||||
if (empty($data) || !is_object($data) || !isset($data->read)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data->read === Table::LEVEL_NO;
|
||||
}
|
||||
|
||||
public function checkReadOnlyOwn(User $user, $data)
|
||||
{
|
||||
if (empty($data) || !is_object($data) || !isset($data->read)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data->read === Table::LEVEL_OWN;
|
||||
}
|
||||
|
||||
public function checkEntity(User $user, Entity $entity, $data, $action)
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->checkScope($user, $data, $action, $entity);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = [])
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_null($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -152,20 +136,20 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
}
|
||||
|
||||
if (is_null($action)) {
|
||||
return true;
|
||||
}
|
||||
if ($data->hasNotNo()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isset($data->$action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $data->$action;
|
||||
$value = $data->get($action);
|
||||
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES || $value === true) {
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$value || $value === Table::LEVEL_NO) {
|
||||
if ($value === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -197,25 +181,30 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
if ($entity->hasAttribute('assignedUserId')) {
|
||||
if ($entity->has('assignedUserId')) {
|
||||
if ($user->id === $entity->get('assignedUserId')) {
|
||||
return true;
|
||||
}
|
||||
if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_ASSIGNED_USER_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_ASSIGNED_USER_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ($entity->hasAttribute('createdById')) {
|
||||
if ($entity->has('createdById')) {
|
||||
if ($user->id === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
else if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_CREATED_BY_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField('assignedUsers')) {
|
||||
if ($entity->hasLinkMultipleId('assignedUsers', $user->id)) {
|
||||
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
|
||||
if ($entity->hasLinkMultipleId(self::FIELD_ASSIGNED_USERS, $user->getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -223,15 +212,18 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity)
|
||||
{
|
||||
$userTeamIdList = $user->getLinkMultipleIdList('teams');
|
||||
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (!$entity->hasRelation('teams') || !$entity->hasAttribute('teamsIds')) {
|
||||
if (!$entity->hasRelation(self::FIELD_TEAMS) || !$entity->hasAttribute(self::ATTR_ASSIGNED_TEAMS_IDS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entityTeamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
$entityTeamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (empty($entityTeamIdList)) {
|
||||
return false;
|
||||
@@ -246,7 +238,7 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, $data)
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data) : bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
@@ -256,32 +248,28 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_object($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->edit === Table::LEVEL_NO && $data->create === Table::LEVEL_NO) {
|
||||
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->config->get('aclAllowDeleteCreated') ||
|
||||
!$entity->has('createdById') ||
|
||||
!$entity->get('createdById') !== $user->getId()
|
||||
!$entity->has(self::ATTR_CREATED_BY_ID) ||
|
||||
!$entity->get(self::ATTR_CREATED_BY_ID) !== $user->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isDeletedAllowed = false;
|
||||
|
||||
if (!$entity->has('assignedUserId')) {
|
||||
if (!$entity->has(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else {
|
||||
if (!$entity->get('assignedUserId')) {
|
||||
if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else if ($entity->get('assignedUserId') === $entity->get('createdById')) {
|
||||
else if ($entity->get(self::ATTR_ASSIGNED_USER_ID) === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
}
|
||||
@@ -308,22 +296,48 @@ class Acl implements ScopeAcl, EntityAcl
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOwnerUserIdAttribute(Entity $entity)
|
||||
public function getOwnerUserIdAttribute(Entity $entity) : ?string
|
||||
{
|
||||
if ($this->ownerUserIdAttribute) {
|
||||
return $this->ownerUserIdAttribute;
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField('assignedUsers')) {
|
||||
return 'assignedUsersIds';
|
||||
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
|
||||
return self::ATTR_ASSIGNED_USERS_IDS;
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('assignedUserId')) {
|
||||
return 'assignedUserId';
|
||||
if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
return self::ATTR_ASSIGNED_USER_ID;
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('createdById')) {
|
||||
return 'createdById';
|
||||
if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
return self::ATTR_CREATED_BY_ID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->config`.
|
||||
*/
|
||||
protected function getConfig() : Config
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->entityManager`.
|
||||
*/
|
||||
protected function getEntityManager() : EntityManager
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->aclManager`.
|
||||
*/
|
||||
protected function getAclManager() : AclManager
|
||||
{
|
||||
return $this->aclManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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\ClassFinder,
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class AclFactory
|
||||
{
|
||||
protected $baseClassName = Acl::class;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->classFinder = $classFinder;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(string $scope) : ScopeAcl
|
||||
{
|
||||
$className = $this->classFinder->find('Acl', $scope);
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseClassName;
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope, // todo remove ?
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,6 @@ namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Interfaces\Injectable;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
AclManager,
|
||||
@@ -49,10 +46,15 @@ class Base extends Acl implements Injectable
|
||||
|
||||
protected $injections = [];
|
||||
|
||||
public function __construct(string $scope, EntityManager $entityManager, AclManager $aclManager, Config $config)
|
||||
{
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
AclManager $aclManager,
|
||||
Config $config,
|
||||
?string $scope = null
|
||||
) {
|
||||
$this->scope = $scope;
|
||||
parent::__construct($scope, $entityManager, $aclManager, $config);
|
||||
|
||||
parent::__construct($entityManager, $aclManager, $config, $scope);
|
||||
|
||||
$this->init();
|
||||
}
|
||||
@@ -62,7 +64,6 @@ class Base extends Acl implements Injectable
|
||||
$this->injections[$name] = $object;
|
||||
}
|
||||
|
||||
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* @todo Add methods.
|
||||
*/
|
||||
interface EntityAcl
|
||||
{
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action = Table::ACTION_READ): bool;
|
||||
|
||||
public function getOwnerUserIdAttribute(Entity $entity): ?string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityCreateAcl
|
||||
{
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityDeleteAcl
|
||||
{
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityEditAcl
|
||||
{
|
||||
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityReadAcl
|
||||
{
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityStreamAcl
|
||||
{
|
||||
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -29,10 +29,9 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
/**
|
||||
* @todo Add methods.
|
||||
*/
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface ScopeAcl
|
||||
{
|
||||
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class ScopeData
|
||||
/**
|
||||
* Get a raw value.
|
||||
*
|
||||
* @return StdClass|boo
|
||||
* @return StdClass|bool
|
||||
*/
|
||||
public function getRaw()
|
||||
{
|
||||
@@ -61,11 +61,17 @@ class ScopeData
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is of boolean type.
|
||||
*/
|
||||
public function isBoolean() : bool
|
||||
{
|
||||
return $this->isBoolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is true.
|
||||
*/
|
||||
public function isTrue() : bool
|
||||
{
|
||||
if (!$this->isBoolean) {
|
||||
@@ -75,6 +81,9 @@ class ScopeData
|
||||
return $this->raw === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is false.
|
||||
*/
|
||||
public function isFalse() : bool
|
||||
{
|
||||
if (!$this->isBoolean) {
|
||||
@@ -84,31 +93,63 @@ class ScopeData
|
||||
return $this->raw === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has any level other than 'no'.
|
||||
*/
|
||||
public function hasNotNo() : bool
|
||||
{
|
||||
foreach ($this->actionData as $level) {
|
||||
if ($level !== Table::LEVEL_NO) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a level for an action.
|
||||
*/
|
||||
public function get(string $action) : string
|
||||
{
|
||||
return $this->actionData[$action] ?? Table::LEVEL_NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 'read' level.
|
||||
*/
|
||||
public function getRead() : string
|
||||
{
|
||||
return $this->get(Table::ACTION_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 'stream' level.
|
||||
*/
|
||||
public function getStream() : string
|
||||
{
|
||||
return $this->get(Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 'create' level.
|
||||
*/
|
||||
public function getCreate() : string
|
||||
{
|
||||
return $this->get(Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an 'edit' level.
|
||||
*/
|
||||
public function getEdit() : string
|
||||
{
|
||||
return $this->get(Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 'delete' level.
|
||||
*/
|
||||
public function getDelete() : string
|
||||
{
|
||||
return $this->get(Table::ACTION_DELETE);
|
||||
|
||||
@@ -39,6 +39,7 @@ use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
Utils\ObjectUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
@@ -170,22 +171,22 @@ class Table
|
||||
|
||||
public function getMap() : StdClass
|
||||
{
|
||||
return $this->data;
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
|
||||
public function getScopeData(string $scope)
|
||||
public function getScopeData(string $scope) : ScopeData
|
||||
{
|
||||
if (isset($this->data->table->$scope)) {
|
||||
$data = $this->data->table->$scope;
|
||||
|
||||
if (is_string($data)) {
|
||||
return $this->getScopeData($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
if (!isset($this->data->table->$scope)) {
|
||||
return ScopeData::fromRaw(false);
|
||||
}
|
||||
|
||||
return null;
|
||||
$data = $this->data->table->$scope;
|
||||
|
||||
if (is_string($data)) {
|
||||
return $this->getScopeData($data);
|
||||
}
|
||||
|
||||
return ScopeData::fromRaw($data);
|
||||
}
|
||||
|
||||
public function get(string $permission) : ?string
|
||||
|
||||
@@ -29,21 +29,23 @@
|
||||
|
||||
namespace Espo\Core;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
ORM\EntityManager,
|
||||
Acl\Acl as BaseAcl,
|
||||
Acl\AclFactory,
|
||||
Acl\ScopeAcl,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Acl\GlobalRestricton,
|
||||
Acl as UserAclWrapper,
|
||||
Acl\Table as Table,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
Acl\EntityDeleteAcl,
|
||||
Acl\EntityStreamAcl,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
@@ -53,7 +55,7 @@ use StdClass;
|
||||
*/
|
||||
class AclManager
|
||||
{
|
||||
private $implementationHashMap = [];
|
||||
protected $implementationHashMap = [];
|
||||
|
||||
private $tableHashMap = [];
|
||||
|
||||
@@ -61,61 +63,60 @@ class AclManager
|
||||
|
||||
protected $userAclClassName = UserAclWrapper::class;
|
||||
|
||||
protected $baseImplementationClassName = BaseAcl::class;
|
||||
protected const PERMISSION_ASSIGNMENT = 'assignment';
|
||||
|
||||
protected $globalRestricton;
|
||||
private $actionInterfaceMap = [
|
||||
Table::ACTION_CREATE => EntityCreateAcl::class,
|
||||
Table::ACTION_READ => EntityReadAcl::class,
|
||||
Table::ACTION_EDIT => EntityEditAcl::class,
|
||||
Table::ACTION_DELETE => EntityDeleteAcl::class,
|
||||
Table::ACTION_STREAM => EntityStreamAcl::class,
|
||||
];
|
||||
|
||||
protected $injectableFactory;
|
||||
|
||||
protected $classFinder;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $aclFactory;
|
||||
|
||||
protected $globalRestricton;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
ClassFinder $classFinder,
|
||||
EntityManager $entityManager,
|
||||
AclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->classFinder = $classFinder;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
}
|
||||
|
||||
public function getImplementation(string $scope) : ScopeAcl
|
||||
/**
|
||||
* Get an ACL implementation for a scope.
|
||||
*
|
||||
* @return ScopeAcl|EntityAcl
|
||||
*/
|
||||
public function getImplementation(string $scope): ScopeAcl
|
||||
{
|
||||
if (empty($this->implementationHashMap[$scope])) {
|
||||
$className = $this->classFinder->find('Acl', $scope);
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseImplementationClassName;
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new Error("{$className} does not exist.");
|
||||
}
|
||||
|
||||
$acl = $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope,
|
||||
]);
|
||||
|
||||
$this->implementationHashMap[$scope] = $acl;
|
||||
if (!array_key_exists($scope, $this->implementationHashMap)) {
|
||||
$this->implementationHashMap[$scope] = $this->aclFactory->create($scope);
|
||||
}
|
||||
|
||||
return $this->implementationHashMap[$scope];
|
||||
}
|
||||
|
||||
protected function getTable(User $user) : Table
|
||||
protected function getTable(User $user): Table
|
||||
{
|
||||
$key = $user->id;
|
||||
$key = $user->getId();
|
||||
|
||||
if (empty($key)) {
|
||||
if (!$key) {
|
||||
$key = spl_object_hash($user);
|
||||
}
|
||||
|
||||
if (empty($this->tableHashMap[$key])) {
|
||||
if (!array_key_exists($key, $this->tableHashMap)) {
|
||||
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
|
||||
'user' => $user,
|
||||
]);
|
||||
@@ -124,7 +125,7 @@ class AclManager
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
public function getMap(User $user) : StdClass
|
||||
public function getMap(User $user): StdClass
|
||||
{
|
||||
return $this->getTable($user)->getMap();
|
||||
}
|
||||
@@ -132,7 +133,7 @@ class AclManager
|
||||
/**
|
||||
* Get an access level for a specific scope and action.
|
||||
*/
|
||||
public function getLevel(User $user, string $scope, string $action) : string
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return $this->getTable($user)->getHighestLevel($scope, $action);
|
||||
@@ -144,7 +145,7 @@ class AclManager
|
||||
/**
|
||||
* Get a permission. E.g. 'assignment' permission.
|
||||
*/
|
||||
public function get(User $user, string $permission) : ?string
|
||||
public function get(User $user, string $permission): ?string
|
||||
{
|
||||
if (substr($permission, -10) !== 'Permission') {
|
||||
$permission .= 'Permission';
|
||||
@@ -156,49 +157,39 @@ class AclManager
|
||||
/**
|
||||
* Whether there's no 'read' access for a specific scope.
|
||||
*/
|
||||
public function checkReadNo(User $user, string $scope) : bool
|
||||
public function checkReadNo(User $user, string $scope): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return (bool) $this->getImplementation($scope)->checkReadNo($user, $data);
|
||||
return $this->getImplementation($scope)->checkReadNo($user, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'team' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyTeam(User $user, string $scope) : bool
|
||||
public function checkReadOnlyTeam(User $user, string $scope): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return (bool) $this->getImplementation($scope)->checkReadOnlyTeam($user, $data);
|
||||
return $this->getImplementation($scope)->checkReadOnlyTeam($user, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'own' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyOwn(User $user, string $scope) : bool
|
||||
public function checkReadOnlyOwn(User $user, string $scope): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return (bool) $this->getImplementation($scope)->checkReadOnlyOwn($user, $data);
|
||||
return $this->getImplementation($scope)->checkReadOnlyOwn($user, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a scope or entity. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
*
|
||||
* @param string|Entity $subject An entity type or entity.
|
||||
*/
|
||||
public function check(User $user, $subject, ?string $action = null) : bool
|
||||
public function check(User $user, $subject, ?string $action = null): bool
|
||||
{
|
||||
if (is_string($subject)) {
|
||||
return $this->checkScope($user, $subject, $action);
|
||||
@@ -216,9 +207,9 @@ class AclManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to a specific entity (record).
|
||||
* Check access to a specific entity.
|
||||
*/
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ) : bool
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
$scope = $entity->getEntityType();
|
||||
|
||||
@@ -232,17 +223,64 @@ class AclManager
|
||||
|
||||
$methodName = 'checkEntity' . ucfirst($action);
|
||||
|
||||
if (method_exists($impl, $methodName)) {
|
||||
return (bool) $impl->$methodName($user, $entity, $data);
|
||||
$interface = $this->actionInterfaceMap[$action] ?? null;
|
||||
|
||||
if ($interface && $impl instanceof $interface) {
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
}
|
||||
|
||||
return (bool) $impl->checkEntity($user, $entity, $data, $action);
|
||||
if (method_exists($impl, $methodName)) {
|
||||
// For backward compatibility.
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
}
|
||||
|
||||
return $impl->checkEntity($user, $entity, $data, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'read' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'create' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityCreate(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'edit' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityEdit(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'delete' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityDelete(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, Table::ACTION_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'stream' access to a specific entity.
|
||||
*/
|
||||
public function checkEntityStream(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkEntity($entity, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity) : bool
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
|
||||
}
|
||||
@@ -250,7 +288,7 @@ class AclManager
|
||||
/**
|
||||
* Whether a user team list overlaps with teams set in an entity.
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity) : bool
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getImplementation($entity->getEntityType())->checkInTeam($user, $entity);
|
||||
}
|
||||
@@ -258,17 +296,17 @@ class AclManager
|
||||
/**
|
||||
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
*/
|
||||
public function checkScope(User $user, string $scope, ?string $action = null) : bool
|
||||
public function checkScope(User $user, string $scope, ?string $action = null): bool
|
||||
{
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return (bool) $this->getImplementation($scope)->checkScope($user, $data, $action);
|
||||
return $this->getImplementation($scope)->checkScope($user, $data, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use checkUserPermission instead.
|
||||
*/
|
||||
public function checkUser(User $user, string $permission, User $target) : bool
|
||||
public function checkUser(User $user, string $permission, User $target): bool
|
||||
{
|
||||
if ($this->get($user, $permission) === Table::LEVEL_ALL) {
|
||||
return true;
|
||||
@@ -309,7 +347,7 @@ class AclManager
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getGlobalRestrictionTypeList(User $user, string $action = Table::ACTION_READ) : array
|
||||
protected function getGlobalRestrictionTypeList(User $user, string $action = Table::ACTION_READ): array
|
||||
{
|
||||
$typeList = [
|
||||
GlobalRestricton::TYPE_FORBIDDEN,
|
||||
@@ -338,8 +376,11 @@ class AclManager
|
||||
* Get attributes forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
User $user,
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
|
||||
@@ -350,7 +391,10 @@ class AclManager
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedAttributeList($scope, $this->getGlobalRestrictionTypeList($user, $action))
|
||||
$this->getScopeRestrictedAttributeList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
@@ -363,8 +407,11 @@ class AclManager
|
||||
* Get fields forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
User $user,
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
|
||||
@@ -375,7 +422,10 @@ class AclManager
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedFieldList($scope, $this->getGlobalRestrictionTypeList($user, $action))
|
||||
$this->getScopeRestrictedFieldList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
@@ -388,15 +438,21 @@ class AclManager
|
||||
* Get links forbidden for a user.
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
User $user,
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedLinkList($scope, $this->getGlobalRestrictionTypeList($user, $action))
|
||||
$this->getScopeRestrictedLinkList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
@@ -410,13 +466,14 @@ class AclManager
|
||||
*
|
||||
* @param User|string $target User entity or user ID.
|
||||
*/
|
||||
public function checkUserPermission(User $user, $target, string $permissionType = 'user') : bool
|
||||
public function checkUserPermission(User $user, $target, string $permissionType = 'user'): bool
|
||||
{
|
||||
$permission = $this->get($user, $permissionType);
|
||||
|
||||
if (is_object($target)) {
|
||||
$userId = $target->id;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$userId = $target;
|
||||
}
|
||||
|
||||
@@ -435,7 +492,11 @@ class AclManager
|
||||
if ($permission === Table::LEVEL_TEAM) {
|
||||
$teamIdList = $user->getLinkMultipleIdList('teams');
|
||||
|
||||
if (!$this->entityManager->getRepository('User')->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
|
||||
if (
|
||||
!$this->entityManager
|
||||
->getRepository('User')
|
||||
->checkBelongsToAnyOfTeams($userId, $teamIdList)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -448,23 +509,24 @@ class AclManager
|
||||
*
|
||||
* @param User|string $target User entity or user ID.
|
||||
*/
|
||||
public function checkAssignmentPermission(User $user, $target) : bool
|
||||
public function checkAssignmentPermission(User $user, $target): bool
|
||||
{
|
||||
return $this->checkUserPermission($user, $target, 'assignment');
|
||||
return $this->checkUserPermission($user, $target, self::PERMISSION_ASSIGNMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a specific user.
|
||||
*/
|
||||
public function createUserAcl(User $user) : UserAclWrapper
|
||||
public function createUserAcl(User $user): UserAclWrapper
|
||||
{
|
||||
$className = $this->userAclClassName;
|
||||
|
||||
$acl = new $className($this, $user);
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
public function getScopeRestrictedFieldList(string $scope, $type) : array
|
||||
public function getScopeRestrictedFieldList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
@@ -481,7 +543,7 @@ class AclManager
|
||||
return $this->globalRestricton->getScopeRestrictedFieldList($scope, $type);
|
||||
}
|
||||
|
||||
public function getScopeRestrictedAttributeList(string $scope, $type) : array
|
||||
public function getScopeRestrictedAttributeList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
@@ -498,7 +560,7 @@ class AclManager
|
||||
return $this->globalRestricton->getScopeRestrictedAttributeList($scope, $type);
|
||||
}
|
||||
|
||||
public function getScopeRestrictedLinkList(string $scope, $type) : array
|
||||
public function getScopeRestrictedLinkList(string $scope, $type): array
|
||||
{
|
||||
if (is_array($type)) {
|
||||
$typeList = $type;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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\AclPortal;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class AclFactory
|
||||
{
|
||||
protected $baseClassName = Acl::class;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->classFinder = $classFinder;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(string $scope) : PortalScopeAcl
|
||||
{
|
||||
$className = $this->classFinder->find('AclPortal', $scope);
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseClassName;
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope, // todo remove ?
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -30,29 +30,28 @@
|
||||
namespace Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
};
|
||||
|
||||
trait Portal
|
||||
{
|
||||
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = [])
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
protected function checkScopeInternal(
|
||||
User $user,
|
||||
ScopeData $data,
|
||||
?string $action = null,
|
||||
?Entity $entity = null,
|
||||
array $entityAccessData = []
|
||||
) : bool {
|
||||
|
||||
if (is_null($data)) {
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,13 +77,9 @@ trait Portal
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isset($data->$action)) {
|
||||
return false;
|
||||
}
|
||||
$value = $data->get($action);
|
||||
|
||||
$value = $data->$action;
|
||||
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES || $value === true) {
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -115,14 +110,13 @@ trait Portal
|
||||
if ($inAccount) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (is_null($isOwnContact) && $entity) {
|
||||
$isOwnContact = $this->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
if ($isOwnContact) {
|
||||
return true;
|
||||
}
|
||||
if (is_null($isOwnContact) && $entity) {
|
||||
$isOwnContact = $this->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
if ($isOwnContact) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,24 +133,19 @@ trait Portal
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkReadOnlyAccount(User $user, $data)
|
||||
public function checkReadOnlyAccount(User $user, ScopeData $data) : bool
|
||||
{
|
||||
if (empty($data) || !is_object($data) || !isset($data->read)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data->read === Table::LEVEL_ACCOUNT;
|
||||
return $data->getRead() === Table::LEVEL_ACCOUNT;
|
||||
}
|
||||
|
||||
public function checkReadOnlyContact(User $user, $data)
|
||||
public function checkReadOnlyContact(User $user, ScopeData $data) : bool
|
||||
{
|
||||
if (empty($data) || !is_object($data) || !isset($data->read)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data->read === Table::LEVEL_CONTACT;
|
||||
return $data->getRead() === Table::LEVEL_CONTACT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
if ($entity->hasAttribute('createdById')) {
|
||||
@@ -168,6 +157,9 @@ trait Portal
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkInAccount(User $user, Entity $entity)
|
||||
{
|
||||
$accountIdList = $user->getLinkMultipleIdList('accounts');
|
||||
@@ -204,6 +196,9 @@ trait Portal
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwnContact(User $user, Entity $entity)
|
||||
{
|
||||
$contactId = $user->get('contactId');
|
||||
|
||||
@@ -35,22 +35,22 @@ use Espo\Core\Acl as BaseAcl;
|
||||
|
||||
class Acl extends BaseAcl
|
||||
{
|
||||
public function checkReadOnlyAccount(string $scope) : bool
|
||||
public function checkReadOnlyAccount(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyAccount($this->user, $scope);
|
||||
}
|
||||
|
||||
public function checkReadOnlyContact(string $scope) : bool
|
||||
public function checkReadOnlyContact(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyContact($this->user, $scope);
|
||||
}
|
||||
|
||||
public function checkInAccount(Entity $entity) : bool
|
||||
public function checkInAccount(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkInAccount($this->user, $entity);
|
||||
}
|
||||
|
||||
public function checkIsOwnContact(Entity $entity) : bool
|
||||
public function checkIsOwnContact(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkIsOwnContact($this->user, $entity);
|
||||
}
|
||||
|
||||
@@ -31,18 +31,22 @@ namespace Espo\Core\Portal;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\Error,
|
||||
AclPortal\Table as Table,
|
||||
AclPortal\Acl as BasePortalAcl,
|
||||
AclPortal\PortalScopeAcl,
|
||||
AclPortal\AclFactory as PortalAclFactory,
|
||||
Acl\ScopeAcl,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Acl\Table as TableBase,
|
||||
Portal\Acl as UserAclWrapper,
|
||||
AclManager as BaseAclManager,
|
||||
InjectableFactory,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
@@ -52,57 +56,44 @@ class AclManager extends BaseAclManager
|
||||
{
|
||||
protected $tableClassName = Table::class;
|
||||
|
||||
private $mainManager = null;
|
||||
|
||||
private $portal = null;
|
||||
|
||||
protected $userAclClassName = UserAclWrapper::class;
|
||||
|
||||
protected $baseImplementationClassName = BasePortalAcl::class;
|
||||
|
||||
public function getImplementation(string $scope) : ScopeAcl
|
||||
private $mainManager = null;
|
||||
|
||||
private $portal = null;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
EntityManager $entityManager,
|
||||
PortalAclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory,
|
||||
BaseAclManager $mainManager
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
$this->mainManager = $mainManager;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
}
|
||||
|
||||
public function getImplementation(string $scope): ScopeAcl
|
||||
{
|
||||
if (empty($this->implementationHashMap[$scope])) {
|
||||
$className = $this->classFinder->find('AclPortal', $scope);
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseImplementationClassName;
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new Error("{$className} does not exist.");
|
||||
}
|
||||
|
||||
$acl = $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope,
|
||||
]);
|
||||
|
||||
$this->implementationHashMap[$scope] = $acl;
|
||||
|
||||
if (!$acl instanceof PortalScopeAcl) {
|
||||
throw new Error("Portal\AclManager: Implementation should be instance of PortalScopeAcl.");
|
||||
}
|
||||
if (!array_key_exists($scope, $this->implementationHashMap)) {
|
||||
$this->implementationHashMap[$scope] = $this->aclFactory->create($scope);
|
||||
}
|
||||
|
||||
return $this->implementationHashMap[$scope];
|
||||
}
|
||||
|
||||
public function setMainManager(BaseAclManager $mainManager) : void
|
||||
{
|
||||
$this->mainManager = $mainManager;
|
||||
}
|
||||
|
||||
protected function getMainManager() : BaseAclManager
|
||||
{
|
||||
return $this->mainManager;
|
||||
}
|
||||
|
||||
public function setPortal(Portal $portal) : void
|
||||
public function setPortal(Portal $portal): void
|
||||
{
|
||||
$this->portal = $portal;
|
||||
}
|
||||
|
||||
protected function getPortal() : Portal
|
||||
protected function getPortal(): Portal
|
||||
{
|
||||
if (!$this->portal) {
|
||||
throw new RuntimeException("Portal is not set.");
|
||||
@@ -111,7 +102,7 @@ class AclManager extends BaseAclManager
|
||||
return $this->portal;
|
||||
}
|
||||
|
||||
protected function getTable(User $user) : TableBase
|
||||
protected function getTable(User $user): TableBase
|
||||
{
|
||||
$key = $user->id;
|
||||
|
||||
@@ -129,175 +120,173 @@ class AclManager extends BaseAclManager
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
public function checkReadOnlyAccount(User $user, string $scope) : bool
|
||||
public function checkReadOnlyAccount(User $user, string $scope): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return $this->getImplementation($scope)->checkReadOnlyAccount($user, $data);
|
||||
}
|
||||
|
||||
public function checkReadOnlyContact(User $user, string $scope) : bool
|
||||
public function checkReadOnlyContact(User $user, string $scope): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return $this->getImplementation($scope)->checkReadOnlyContact($user, $data);
|
||||
}
|
||||
|
||||
public function checkInAccount(User $user, Entity $entity) : bool
|
||||
public function checkInAccount(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getImplementation($entity->getEntityType())->checkInAccount($user, $entity);
|
||||
}
|
||||
|
||||
public function checkIsOwnContact(User $user, Entity $entity) : bool
|
||||
public function checkIsOwnContact(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
public function getMap(User $user) : StdClass
|
||||
public function getMap(User $user): StdClass
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->getMap($user);
|
||||
return $this->mainManager->getMap($user);
|
||||
}
|
||||
|
||||
return parent::getMap($user);
|
||||
}
|
||||
|
||||
public function getLevel(User $user, string $scope, string $action) : string
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->getLevel($user, $scope, $action);
|
||||
return $this->mainManager->getLevel($user, $scope, $action);
|
||||
}
|
||||
|
||||
return parent::getLevel($user, $scope, $action);
|
||||
}
|
||||
|
||||
public function get(User $user, string $permission) : ?string
|
||||
public function get(User $user, string $permission): ?string
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->get($user, $permission);
|
||||
return $this->mainManager->get($user, $permission);
|
||||
}
|
||||
|
||||
return parent::get($user, $permission);
|
||||
}
|
||||
|
||||
public function checkReadOnlyTeam(User $user, string $scope) : bool
|
||||
public function checkReadOnlyTeam(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return $this->getMainManager()->checkReadOnlyTeam($user, $data);
|
||||
return $this->mainManager->checkReadOnlyTeam($user, $data);
|
||||
}
|
||||
|
||||
return parent::checkReadOnlyTeam($user, $scope);
|
||||
}
|
||||
|
||||
public function checkReadNo(User $user, string $scope) : bool
|
||||
public function checkReadNo(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return $this->getMainManager()->checkReadNo($user, $data);
|
||||
return $this->mainManager->checkReadNo($user, $data);
|
||||
}
|
||||
|
||||
return parent::checkReadNo($user, $scope);
|
||||
}
|
||||
|
||||
public function checkReadOnlyOwn(User $user, string $scope) : bool
|
||||
public function checkReadOnlyOwn(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
return $this->getMainManager()->checkReadOnlyOwn($user, $data);
|
||||
return $this->mainManager->checkReadOnlyOwn($user, $data);
|
||||
}
|
||||
|
||||
return parent::checkReadOnlyOwn($user, $scope);
|
||||
}
|
||||
|
||||
public function check(User $user, $subject, ?string $action = null) : bool
|
||||
public function check(User $user, $subject, ?string $action = null): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->check($user, $subject, $action);
|
||||
return $this->mainManager->check($user, $subject, $action);
|
||||
}
|
||||
|
||||
return parent::check($user, $subject, $action);
|
||||
}
|
||||
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ) : bool
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->checkEntity($user, $entity, $action);
|
||||
return $this->mainManager->checkEntity($user, $entity, $action);
|
||||
}
|
||||
|
||||
return parent::checkEntity($user, $entity, $action);
|
||||
}
|
||||
|
||||
public function checkIsOwner(User $user, Entity $entity) : bool
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->checkIsOwner($user, $entity);
|
||||
return $this->mainManager->checkIsOwner($user, $entity);
|
||||
}
|
||||
|
||||
return parent::checkIsOwner($user, $entity);
|
||||
}
|
||||
|
||||
public function checkInTeam(User $user, Entity $entity) : bool
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->checkInTeam($user, $entity);
|
||||
return $this->mainManager->checkInTeam($user, $entity);
|
||||
}
|
||||
|
||||
return parent::checkInTeam($user, $entity);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, string $scope, ?string $action = null) : bool
|
||||
public function checkScope(User $user, string $scope, ?string $action = null): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->checkScope($user, $scope, $action);
|
||||
return $this->mainManager->checkScope($user, $scope, $action);
|
||||
}
|
||||
|
||||
return parent::checkScope($user, $scope, $action);
|
||||
}
|
||||
|
||||
public function checkUser(User $user, string $permission, User $entity) : bool
|
||||
public function checkUser(User $user, string $permission, User $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->checkUser($user, $permission, $entity);
|
||||
return $this->mainManager->checkUser($user, $permission, $entity);
|
||||
}
|
||||
|
||||
return parent::checkUser($user, $permission, $entity);
|
||||
}
|
||||
|
||||
public function getScopeForbiddenAttributeList(
|
||||
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
User $user,
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->mainManager->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
public function getScopeForbiddenFieldList(
|
||||
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
|
||||
) : array {
|
||||
User $user,
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->getMainManager()->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->mainManager->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
protected function checkUserIsNotPortal(User $user) : bool
|
||||
protected function checkUserIsNotPortal(User $user): bool
|
||||
{
|
||||
return !$user->isPortal();
|
||||
}
|
||||
|
||||
@@ -50,10 +50,8 @@ class AclManager implements Loader
|
||||
|
||||
public function load() : PortalAclManager
|
||||
{
|
||||
$aclManager = $this->injectableFactory->create(PortalAclManager::class);
|
||||
|
||||
$aclManager->setMainManager($this->internalAclManager);
|
||||
|
||||
return $aclManager;
|
||||
return $this->injectableFactory->createWith(PortalAclManager::class,[
|
||||
'mainManager' => $this->internalAclManager,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,42 +29,99 @@
|
||||
|
||||
namespace Espo\Entities;
|
||||
|
||||
class Note extends \Espo\Core\ORM\Entity
|
||||
use Espo\Core\ORM\Entity;
|
||||
|
||||
class Note extends Entity
|
||||
{
|
||||
public const TARGET_SELF = 'self';
|
||||
|
||||
public const TARGET_ALL = 'all';
|
||||
|
||||
public const TARGET_TEAMS = 'teams';
|
||||
|
||||
public const TARGET_USERS = 'users';
|
||||
|
||||
public const TARGET_PORTALS = 'portals';
|
||||
|
||||
public const TYPE_POST = 'Post';
|
||||
|
||||
private $aclIsProcessed = false;
|
||||
|
||||
public function setAclIsProcessed()
|
||||
public function isPost(): bool
|
||||
{
|
||||
return $this->getType() === self::TYPE_POST;
|
||||
}
|
||||
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->get('type');
|
||||
}
|
||||
|
||||
public function getTargetType(): ?string
|
||||
{
|
||||
return $this->get('targetType');
|
||||
}
|
||||
|
||||
public function getParentType(): ?string
|
||||
{
|
||||
return $this->get('parentType');
|
||||
}
|
||||
|
||||
public function getParentId(): ?string
|
||||
{
|
||||
return $this->get('parentId');
|
||||
}
|
||||
|
||||
public function isInternal(): bool
|
||||
{
|
||||
return (bool) $this->get('isInternal');
|
||||
}
|
||||
|
||||
public function setAclIsProcessed(): void
|
||||
{
|
||||
$this->aclIsProcessed = true;
|
||||
}
|
||||
|
||||
public function isAclProcessed()
|
||||
public function isAclProcessed(): bool
|
||||
{
|
||||
return $this->aclIsProcessed;
|
||||
return (bool) $this->aclIsProcessed;
|
||||
}
|
||||
|
||||
public function loadAttachments()
|
||||
{
|
||||
$data = $this->get('data');
|
||||
|
||||
if (!empty($data) && !empty($data->attachmentsIds) && is_array($data->attachmentsIds)) {
|
||||
$attachmentsIds = $data->attachmentsIds;
|
||||
$collection = $this->entityManager->getRepository('Attachment')->select(['id', 'name', 'type'])->order('createdAt')->where([
|
||||
'id' => $attachmentsIds
|
||||
])->find();
|
||||
} else {
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getRepository('Attachment')
|
||||
->select(['id', 'name', 'type'])
|
||||
->order('createdAt')
|
||||
->where([
|
||||
'id' => $attachmentsIds
|
||||
])
|
||||
->find();
|
||||
}
|
||||
else {
|
||||
$this->loadLinkMultipleField('attachments');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = array();
|
||||
$names = new \stdClass();
|
||||
$types = new \stdClass();
|
||||
$ids = [];
|
||||
|
||||
$names = (object) [];
|
||||
$types = (object) [];
|
||||
|
||||
foreach ($collection as $e) {
|
||||
$id = $e->id;
|
||||
$ids[] = $id;
|
||||
|
||||
$names->$id = $e->get('name');
|
||||
$types->$id = $e->get('type');
|
||||
}
|
||||
|
||||
$this->set('attachmentsIds', $ids);
|
||||
$this->set('attachmentsNames', $names);
|
||||
$this->set('attachmentsTypes', $types);
|
||||
@@ -73,21 +130,26 @@ class Note extends \Espo\Core\ORM\Entity
|
||||
public function addNotifiedUserId($userId)
|
||||
{
|
||||
$userIdList = $this->get('notifiedUserIdList');
|
||||
|
||||
if (!is_array($userIdList)) {
|
||||
$userIdList = [];
|
||||
}
|
||||
|
||||
if (!in_array($userId, $userIdList)) {
|
||||
$userIdList[] = $userId;
|
||||
}
|
||||
|
||||
$this->set('notifiedUserIdList', $userIdList);
|
||||
}
|
||||
|
||||
public function isUserIdNotified($userId)
|
||||
{
|
||||
$userIdList = $this->get('notifiedUserIdList');
|
||||
|
||||
if (!is_array($userIdList)) {
|
||||
$userIdList = [];
|
||||
}
|
||||
|
||||
return in_array($userId, $userIdList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Base;
|
||||
use Espo\Core\Acl\Acl;
|
||||
|
||||
class CampaignLogRecord extends Base
|
||||
class CampaignLogRecord extends Acl
|
||||
{
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
|
||||
@@ -33,9 +33,9 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Base;
|
||||
use Espo\Core\Acl\Acl;
|
||||
|
||||
class CampaignTrackingUrl extends Base
|
||||
class CampaignTrackingUrl extends Acl
|
||||
{
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
|
||||
@@ -33,9 +33,9 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Base;
|
||||
use Espo\Core\Acl\Acl;
|
||||
|
||||
class MassEmail extends Base
|
||||
class MassEmail extends Acl
|
||||
{
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
|
||||
@@ -33,24 +33,25 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Acl\Base;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Meeting extends Base
|
||||
class Meeting extends Acl implements EntityReadAcl
|
||||
{
|
||||
protected $ownerUserIdAttribute = 'usersIds';
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, $data)
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->read === 'own' || $data->read === 'team') {
|
||||
if ($entity->hasLinkMultipleId('users', $user->id)) {
|
||||
if ($data->getRead() === Table::LEVEL_OWN || $data->getRead() === Table::LEVEL_TEAM) {
|
||||
if ($entity->hasLinkMultipleId('users', $user->getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,18 @@ use Espo\Entities\User as EntityUser;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\AclPortal\Acl;
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class KnowledgeBaseArticle extends Acl
|
||||
class KnowledgeBaseArticle extends Acl implements EntityReadAcl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$this->checkEntity($user, $entity, $data, 'read')) {
|
||||
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+280
-124
@@ -30,11 +30,20 @@
|
||||
namespace Espo\Services;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table as AclTable,
|
||||
};
|
||||
|
||||
use Espo\Entities\Note as NoteEntity;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use StdClass;
|
||||
|
||||
class Note extends Record
|
||||
{
|
||||
protected $noteNotificationPeriod = '1 hour';
|
||||
@@ -42,6 +51,7 @@ class Note extends Record
|
||||
public function loadAdditionalFields(Entity $entity)
|
||||
{
|
||||
parent::loadAdditionalFields($entity);
|
||||
|
||||
$entity->loadAttachments();
|
||||
}
|
||||
|
||||
@@ -49,74 +59,120 @@ class Note extends Record
|
||||
{
|
||||
parent::afterCreateEntity($entity, $data);
|
||||
|
||||
if ($entity->get('type') === 'Post' && $entity->get('parentType') && $entity->get('parentType')) {
|
||||
$preferences = $this->getEntityManager()->getEntity('Preferences', $this->getUser()->id);
|
||||
if ($preferences && $preferences->get('followEntityOnStreamPost')) {
|
||||
if ($this->getMetadata()->get(['scopes', $entity->get('parentType'), 'stream'])) {
|
||||
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
if ($parent && !$this->getUser()->isSystem() && !$this->getUser()->isApi()) {
|
||||
$this->getServiceFactory()->create('Stream')->followEntity($parent, $this->getUser()->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->processFollowAfterCreate($entity);
|
||||
}
|
||||
|
||||
protected function processFollowAfterCreate(NoteEntity $entity): void
|
||||
{
|
||||
$parentType = $entity->get('parentType');
|
||||
$parentId = $entity->get('parentId');
|
||||
|
||||
if ($entity->get('type') !== NoteEntity::TYPE_POST || !$parentType || !$parentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $parentType, 'stream'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preferences = $this->entityManager->getEntity('Preferences', $this->user->getId());
|
||||
|
||||
if (!$preferences) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$preferences->get('followEntityOnStreamPost')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntity($parentType, $parentId);
|
||||
|
||||
if (!$parent || $this->user->isSystem() || $this->user->isApi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getStreamService()->followEntity($parent, $this->user->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NoteEntity $entity
|
||||
* @param StdClass $data
|
||||
*/
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
if (!empty($data->parentType) && !empty($data->parentId)) {
|
||||
$entity = $this->getEntityManager()->getEntity($data->parentType, $data->parentId);
|
||||
if ($entity) {
|
||||
if (!$this->getAcl()->check($entity, 'read')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
$parentType = $data->parentType ?? null;
|
||||
$parentId = $data->parentId ?? null;
|
||||
|
||||
if ($parentType && $parentId) {
|
||||
$entity = $this->entityManager->getEntity($data->parentType, $data->parentId);
|
||||
|
||||
if ($entity && !$this->acl->check($entity, AclTable::ACTION_READ)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
parent::beforeCreateEntity($entity, $data);
|
||||
|
||||
if ($entity->get('type') === 'Post') {
|
||||
if ($entity->isPost()) {
|
||||
$this->handlePostText($entity);
|
||||
}
|
||||
|
||||
$targetType = $entity->get('targetType');
|
||||
$targetType = $entity->getTargetType();
|
||||
|
||||
$entity->clear('isGlobal');
|
||||
|
||||
switch ($targetType) {
|
||||
case 'all':
|
||||
case NoteEntity::TARGET_ALL:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
$entity->set('isGlobal', true);
|
||||
|
||||
break;
|
||||
case 'self':
|
||||
|
||||
case NoteEntity::TARGET_SELF:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
$entity->set('usersIds', [$this->getUser()->id]);
|
||||
$entity->set('usersIds', [$this->user->id]);
|
||||
$entity->set('isForSelf', true);
|
||||
|
||||
break;
|
||||
case 'users':
|
||||
|
||||
case NoteEntity::TARGET_USERS:
|
||||
|
||||
$entity->clear('teamsIds');
|
||||
$entity->clear('portalsIds');
|
||||
|
||||
break;
|
||||
case 'teams':
|
||||
|
||||
case NoteEntity::TARGET_TEAMS:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('portalsIds');
|
||||
|
||||
break;
|
||||
case 'portals':
|
||||
|
||||
case NoteEntity::TARGET_PORTALS:
|
||||
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NoteEntity $entity
|
||||
* @param StdClass $data
|
||||
*/
|
||||
protected function beforeUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeUpdateEntity($entity, $data);
|
||||
|
||||
if ($entity->get('type') === 'Post') {
|
||||
if ($entity->isPost()) {
|
||||
$this->handlePostText($entity);
|
||||
}
|
||||
|
||||
@@ -130,11 +186,16 @@ class Note extends Record
|
||||
protected function handlePostText(Entity $entity)
|
||||
{
|
||||
$post = $entity->get('post');
|
||||
if (empty($post)) return;
|
||||
|
||||
if (empty($post)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$siteUrl = $this->getConfig()->getSiteUrl();
|
||||
|
||||
$regexp = '/' . preg_quote($siteUrl, '/') . '(\/portal|\/portal\/[a-zA-Z0-9]*)?\/#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)/';
|
||||
$regexp = '/' . preg_quote($siteUrl, '/') .
|
||||
'(\/portal|\/portal\/[a-zA-Z0-9]*)?\/#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)/';
|
||||
|
||||
$post = preg_replace($regexp, '[\2/\3](#\2/view/\3)', $post);
|
||||
|
||||
$entity->set('post', $post);
|
||||
@@ -142,73 +203,141 @@ class Note extends Record
|
||||
|
||||
public function processAssignmentCheck(Entity $entity)
|
||||
{
|
||||
if ($entity->isNew()) {
|
||||
$targetType = $entity->get('targetType');
|
||||
if (!$entity->isNew()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($targetType) {
|
||||
$assignmentPermission = $this->getAcl()->get('assignmentPermission');
|
||||
if ($assignmentPermission === false || $assignmentPermission === 'no') {
|
||||
if ($targetType !== 'self') {
|
||||
throw new Forbidden('Not permitted to post to anybody except self.');
|
||||
}
|
||||
}
|
||||
$targetType = $entity->getTargetType();
|
||||
|
||||
if ($targetType === 'teams') {
|
||||
$teamIdList = $entity->get('teamsIds');
|
||||
if (empty($teamIdList) || !is_array($teamIdList)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
}
|
||||
if ($targetType === 'users') {
|
||||
$userIdList = $entity->get('usersIds');
|
||||
if (empty($userIdList) || !is_array($userIdList)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
}
|
||||
if ($targetType === 'portals') {
|
||||
$portalIdList = $entity->get('portalsIds');
|
||||
if (empty($portalIdList) || !is_array($portalIdList)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if ($this->getAcl()->get('portalPermission') !== 'yes') {
|
||||
throw new Forbidden('Not permitted to post to portal users.');
|
||||
}
|
||||
}
|
||||
if (!$targetType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($assignmentPermission === 'team') {
|
||||
if ($targetType === 'all') {
|
||||
throw new Forbidden('Not permitted to post to all.');
|
||||
}
|
||||
$userTeamIdList = $this->user->getTeamIdList();
|
||||
|
||||
$userTeamIdList = $this->getUser()->getTeamIdList();
|
||||
$userIdList = $entity->getLinkMultipleIdList('users');
|
||||
$portalIdList = $entity->getLinkMultipleIdList('portals');
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
|
||||
if ($targetType === 'teams') {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to foreign teams.');
|
||||
}
|
||||
foreach ($teamIdList as $teamId) {
|
||||
if (!in_array($teamId, $userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to foreign teams.');
|
||||
}
|
||||
}
|
||||
} else if ($targetType === 'users') {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
$targetUserList = null;
|
||||
|
||||
foreach ($userIdList as $userId) {
|
||||
if ($userId === $this->getUser()->id) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->getEntityManager()->getRepository('User')->checkBelongsToAnyOfTeams($userId, $userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($targetType === NoteEntity::TARGET_USERS) {
|
||||
$targetUserList = $this->entityManager
|
||||
->getRDBRepository('User')
|
||||
->select(['id', 'type'])
|
||||
->where([
|
||||
'id' => $userIdList,
|
||||
])
|
||||
->find();
|
||||
}
|
||||
|
||||
$hasPortalTargetUser = false;
|
||||
$allTargetUsersArePortal = true;
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if (!$user->isPortal()) {
|
||||
$allTargetUsersArePortal = false;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$hasPortalTargetUser = true;
|
||||
}
|
||||
}
|
||||
|
||||
$assignmentPermission = $this->acl->get('assignment');
|
||||
|
||||
if ($assignmentPermission === AclTable::LEVEL_NO) {
|
||||
if (
|
||||
$targetType !== NoteEntity::TARGET_SELF &&
|
||||
$targetType !== NoteEntity::TARGET_PORTALS &&
|
||||
!(
|
||||
$targetType === NoteEntity::TARGET_USERS &&
|
||||
count($userIdList) === 1 &&
|
||||
$userIdList[0] === $this->user->getId()
|
||||
) &&
|
||||
!(
|
||||
$targetType === NoteEntity::TARGET_USERS && $allTargetUsersArePortal
|
||||
)
|
||||
) {
|
||||
throw new Forbidden('Not permitted to post to anybody except self.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === NoteEntity::TARGET_TEAMS) {
|
||||
if (empty($teamIdList)) {
|
||||
throw new BadRequest("No team IDS.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === NoteEntity::TARGET_USERS) {
|
||||
if (empty($userIdList)) {
|
||||
throw new BadRequest("No user IDs.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === NoteEntity::TARGET_PORTALS) {
|
||||
if (empty($portalIdList)) {
|
||||
throw new BadRequest("No portal IDs.");
|
||||
}
|
||||
|
||||
if ($this->acl->get('portal') !== AclTable::LEVEL_YES) {
|
||||
throw new Forbidden('Not permitted to post to portal users.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetType === NoteEntity::TARGET_USERS && $this->acl->get('portal') !== AclTable::LEVEL_YES) {
|
||||
if ($hasPortalTargetUser) {
|
||||
throw new Forbidden('Not permitted to post to portal users.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($assignmentPermission === AclTable::LEVEL_TEAM) {
|
||||
if ($targetType === NoteEntity::TARGET_ALL) {
|
||||
throw new Forbidden('Not permitted to post to all.');
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$assignmentPermission === AclTable::LEVEL_TEAM &&
|
||||
$targetType === NoteEntity::TARGET_TEAMS
|
||||
) {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to foreign teams.');
|
||||
}
|
||||
|
||||
foreach ($teamIdList as $teamId) {
|
||||
if (!in_array($teamId, $userTeamIdList)) {
|
||||
throw new Forbidden("Not permitted to post to foreign teams.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$assignmentPermission === AclTable::LEVEL_TEAM &&
|
||||
$targetType === NoteEntity::TARGET_USERS
|
||||
) {
|
||||
if (empty($userTeamIdList)) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
|
||||
foreach ($targetUserList as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($user->isPortal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inTeam = $this->entityManager
|
||||
->getRepository('User')
|
||||
->checkBelongsToAnyOfTeams($user->getId(), $userTeamIdList);
|
||||
|
||||
if (!$inTeam) {
|
||||
throw new Forbidden('Not permitted to post to users from foreign teams.');
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function link(string $id, string $link, string $foreignId) : void
|
||||
@@ -229,29 +358,39 @@ class Note extends Record
|
||||
parent::unlink($id, $link, $foreignId);
|
||||
}
|
||||
|
||||
public function processNoteAclJob($data)
|
||||
public function processNoteAclJob(StdClass $data): void
|
||||
{
|
||||
$targetType = $data->targetType;
|
||||
$targetId = $data->targetId;
|
||||
|
||||
if ($targetType && $targetId && $this->getEntityManager()->hasRepository($targetType)) {
|
||||
$entity = $this->getEntityManager()->getEntity($targetType, $targetId);
|
||||
if ($targetType && $targetId && $this->entityManager->hasRepository($targetType)) {
|
||||
$entity = $this->entityManager->getEntity($targetType, $targetId);
|
||||
|
||||
if ($entity) {
|
||||
$this->processNoteAcl($entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function processNoteAcl(Entity $entity, $forceProcessNoteNotifications = false)
|
||||
public function processNoteAcl(Entity $entity, bool $forceProcessNoteNotifications = false): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) return;
|
||||
if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->getMetadata()->get(['scopes', $entityType, 'acl'])) return;
|
||||
if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) return;
|
||||
if (!$this->getMetadata()->get(['scopes', $entityType, 'acl'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ownerUserIdAttribute = $this->getAclManager()->getImplementation($entityType)->getOwnerUserIdAttribute($entity);
|
||||
if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ownerUserIdAttribute = $this->getAclManager()
|
||||
->getImplementation($entityType)
|
||||
->getOwnerUserIdAttribute($entity);
|
||||
|
||||
$usersAttributeIsChanged = false;
|
||||
$teamsAttributeIsChanged = false;
|
||||
@@ -264,12 +403,16 @@ class Note extends Record
|
||||
if ($usersAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
if ($entity->getAttributeParam($ownerUserIdAttribute, 'isLinkMultipleIdList')) {
|
||||
$userLink = $entity->getAttributeParam($ownerUserIdAttribute, 'relation');
|
||||
|
||||
$userIdList = $entity->getLinkMultipleIdList($userLink);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$userId = $entity->get($ownerUserIdAttribute);
|
||||
|
||||
if ($userId) {
|
||||
$userIdList = [$userId];
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$userIdList = [];
|
||||
}
|
||||
}
|
||||
@@ -280,62 +423,75 @@ class Note extends Record
|
||||
if ($entity->isAttributeChanged('teamsIds')) {
|
||||
$teamsAttributeIsChanged = true;
|
||||
}
|
||||
|
||||
if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$teamIdList = $entity->getLinkMultipleIdList('teams');
|
||||
}
|
||||
}
|
||||
|
||||
if ($usersAttributeIsChanged || $teamsAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$noteList = $this->getEntityManager()->getRepository('Note')->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType
|
||||
],
|
||||
[
|
||||
'parentId' => $entity->id,
|
||||
'parentType' => $entityType,
|
||||
'superParentId!=' => null,
|
||||
'relatedId' => null
|
||||
$noteList = $this->entityManager
|
||||
->getRepository('Note')
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType
|
||||
],
|
||||
[
|
||||
'parentId' => $entity->id,
|
||||
'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 = [];
|
||||
|
||||
if (!empty($forceProcessNoteNotifications)) {
|
||||
$noteOptions['forceProcessNotifications'] = true;
|
||||
}
|
||||
|
||||
$period = '-' . $this->getConfig()->get('noteNotificationPeriod', $this->noteNotificationPeriod);
|
||||
$threshold = new \DateTime();
|
||||
|
||||
$threshold = new DateTime();
|
||||
|
||||
$threshold->modify($period);
|
||||
|
||||
foreach ($noteList as $note) {
|
||||
if (!$entity->isNew()) {
|
||||
try {
|
||||
$createdAtDt = new \DateTime($note->get('createdAt'));
|
||||
$createdAtDt = new DateTime($note->get('createdAt'));
|
||||
|
||||
if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) {
|
||||
continue;
|
||||
}
|
||||
} catch (\Exception $e) {};
|
||||
}
|
||||
catch (Exception $e) {};
|
||||
}
|
||||
|
||||
if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$note->set('teamsIds', $teamIdList);
|
||||
}
|
||||
|
||||
if ($usersAttributeIsChanged || $forceProcessNoteNotifications) {
|
||||
$note->set('usersIds', $userIdList);
|
||||
}
|
||||
$this->getEntityManager()->saveEntity($note, $noteOptions);
|
||||
|
||||
$this->entityManager->saveEntity($note, $noteOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\Api;
|
||||
namespace tests\unit\Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
@@ -105,6 +105,8 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals($raw, $data->getRaw());
|
||||
|
||||
$this->assertNotSame($raw, $data->getRaw());
|
||||
|
||||
$this->assertTrue($data->hasNotNo());
|
||||
}
|
||||
|
||||
public function testRecordEmpty()
|
||||
@@ -114,5 +116,23 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
$data = ScopeData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
|
||||
|
||||
$this->assertFalse($data->hasNotNo());
|
||||
}
|
||||
|
||||
public function testRecordOnlyNo()
|
||||
{
|
||||
$raw = (object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_NO,
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
Table::ACTION_EDIT => Table::LEVEL_NO,
|
||||
Table::ACTION_DELETE => Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
$data = ScopeData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
|
||||
|
||||
$this->assertFalse($data->hasNotNo());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user