diff --git a/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyOwn.php b/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyOwn.php index f5f2c93bb3..3f8d1bcc08 100644 --- a/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyOwn.php +++ b/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyOwn.php @@ -29,34 +29,23 @@ namespace Espo\Classes\Select\Email\AccessControlFilters; -use Espo\Core\{ - Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Classes\Select\Email\Helpers\JoinHelper, - Entities\User, -}; +use Espo\Classes\Select\Email\Helpers\JoinHelper; +use Espo\Entities\User; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class OnlyOwn implements Filter { - private $user; - - private $joinHelper; - - public function __construct(User $user, JoinHelper $joinHelper) - { - $this->user = $user; - $this->joinHelper = $joinHelper; - } + public function __construct( + private User $user, + private JoinHelper $joinHelper + ) {} public function apply(QueryBuilder $queryBuilder): void { $this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId()); - $queryBuilder->where([ - 'emailUser.userId' => $this->user->getId(), - ]); + $queryBuilder->where(['emailUser.userId' => $this->user->getId()]); } } diff --git a/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyTeam.php b/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyTeam.php index eb7c33acf5..e8bca7ae29 100644 --- a/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyTeam.php +++ b/application/Espo/Classes/Select/Email/AccessControlFilters/OnlyTeam.php @@ -29,39 +29,47 @@ namespace Espo\Classes\Select\Email\AccessControlFilters; -use Espo\Core\{ - Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Classes\Select\Email\Helpers\JoinHelper, - Entities\User, -}; +use Espo\Entities\Email; +use Espo\Entities\Team; +use Espo\Entities\User; +use Espo\ORM\Query\Part\Condition as Cond; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class OnlyTeam implements Filter { - private $user; - - private $joinHelper; - - public function __construct(User $user, JoinHelper $joinHelper) - { - $this->user = $user; - $this->joinHelper = $joinHelper; - } + public function __construct(private User $user) + {} public function apply(QueryBuilder $queryBuilder): void { - $this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId()); - - $queryBuilder->distinct(); - $queryBuilder->leftJoin('teams', 'teamsAccess'); - $queryBuilder->where([ - 'OR' => [ - 'teamsAccessMiddle.teamId' => $this->user->getLinkMultipleIdList('teams'), + $subQuery = QueryBuilder::create() + ->select('id') + ->from(Email::ENTITY_TYPE) + ->leftJoin(Team::RELATIONSHIP_ENTITY_TEAM, 'entityTeam', [ + 'entityTeam.entityId:' => 'id', + 'entityTeam.entityType' => Email::ENTITY_TYPE, + 'entityTeam.deleted' => false, + ]) + ->leftJoin(Email::RELATIONSHIP_EMAIL_USER, 'emailUser', [ + 'emailUser.emailId:' => 'id', + 'emailUser.deleted' => false, 'emailUser.userId' => $this->user->getId(), - ] - ]); + ]) + ->where([ + 'OR' => [ + 'entityTeam.teamId' => $this->user->getTeamIdList(), + 'emailUser.userId' => $this->user->getId(), + ] + ]) + ->build(); + + $queryBuilder->where( + Cond::in( + Cond::column('id'), + $subQuery + ) + ); } } diff --git a/application/Espo/Classes/Select/Email/Helpers/JoinHelper.php b/application/Espo/Classes/Select/Email/Helpers/JoinHelper.php index 068044de25..6c884c82b0 100644 --- a/application/Espo/Classes/Select/Email/Helpers/JoinHelper.php +++ b/application/Espo/Classes/Select/Email/Helpers/JoinHelper.php @@ -29,9 +29,8 @@ namespace Espo\Classes\Select\Email\Helpers; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, -}; +use Espo\Entities\Email; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class JoinHelper { @@ -41,14 +40,10 @@ class JoinHelper return; } - $queryBuilder->leftJoin( - 'EmailUser', - 'emailUser', - [ - 'emailUser.emailId:' => 'id', - 'emailUser.deleted' => false, - 'emailUser.userId' => $userId, - ] - ); + $queryBuilder->leftJoin(Email::RELATIONSHIP_EMAIL_USER, 'emailUser', [ + 'emailUser.emailId:' => 'id', + 'emailUser.deleted' => false, + 'emailUser.userId' => $userId, + ]); } } diff --git a/application/Espo/Classes/Select/Email/TextFilter.php b/application/Espo/Classes/Select/Email/TextFilter.php index d8aae95f2c..4f6e6af7c0 100644 --- a/application/Espo/Classes/Select/Email/TextFilter.php +++ b/application/Espo/Classes/Select/Email/TextFilter.php @@ -29,6 +29,7 @@ namespace Espo\Classes\Select\Email; +use Espo\Core\Exceptions\Error; use Espo\Core\Select\Text\Filter; use Espo\Core\Select\Text\Filter\Data; use Espo\Core\Select\Text\DefaultFilter; @@ -44,22 +45,15 @@ use Espo\Entities\EmailAddress; class TextFilter implements Filter { - private $defaultFilter; - - private $config; - - private $entityManager; - public function __construct( - DefaultFilter $defaultFilter, - ConfigProvider $config, - EntityManager $entityManager - ) { - $this->defaultFilter = $defaultFilter; - $this->config = $config; - $this->entityManager = $entityManager; - } + private DefaultFilter $defaultFilter, + private ConfigProvider $config, + private EntityManager $entityManager + ) {} + /** + * @throws Error + */ public function apply(QueryBuilder $queryBuilder, Data $data): void { $filter = $data->getFilter(); @@ -67,7 +61,7 @@ class TextFilter implements Filter if ( mb_strlen($filter) < $this->config->getMinLengthForContentSearch() || - strpos($filter, '@') === false || + !str_contains($filter, '@') || $data->forceFullTextSearch() ) { $this->defaultFilter->apply($queryBuilder, $data); diff --git a/application/Espo/Classes/Select/Team/AccessControlFilters/OnlyTeam.php b/application/Espo/Classes/Select/Team/AccessControlFilters/OnlyTeam.php index 53bcea1124..1ec4f99df8 100644 --- a/application/Espo/Classes/Select/Team/AccessControlFilters/OnlyTeam.php +++ b/application/Espo/Classes/Select/Team/AccessControlFilters/OnlyTeam.php @@ -30,22 +30,14 @@ namespace Espo\Classes\Select\Team\AccessControlFilters; use Espo\Entities\User; - use Espo\Core\Select\AccessControl\Filter; - -use Espo\ORM\Query\{ - SelectBuilder, - Part\Condition as Cond, -}; +use Espo\ORM\Query\Part\Condition as Cond; +use Espo\ORM\Query\SelectBuilder; class OnlyTeam implements Filter { - private $user; - - public function __construct(User $user) - { - $this->user = $user; - } + public function __construct(private User $user) + {} public function apply(SelectBuilder $queryBuilder): void { diff --git a/application/Espo/Classes/Select/User/AccessControlFilters/OnlyTeam.php b/application/Espo/Classes/Select/User/AccessControlFilters/OnlyTeam.php index 5f991a49c7..1eb6e57276 100644 --- a/application/Espo/Classes/Select/User/AccessControlFilters/OnlyTeam.php +++ b/application/Espo/Classes/Select/User/AccessControlFilters/OnlyTeam.php @@ -31,25 +31,18 @@ namespace Espo\Classes\Select\User\AccessControlFilters; use Espo\ORM\Query\SelectBuilder; -use Espo\Core\{ - Select\AccessControl\Filter, - AclManager, - Acl\Table, -}; +use Espo\Core\Acl\Table; +use Espo\Core\AclManager; +use Espo\Core\Select\AccessControl\Filter; use Espo\Entities\User; class OnlyTeam implements Filter { - private $user; - - private $aclManager; - - public function __construct(User $user, AclManager $aclManager) - { - $this->user = $user; - $this->aclManager = $aclManager; - } + public function __construct( + private User $user, + private AclManager $aclManager + ) {} public function apply(SelectBuilder $queryBuilder): void { @@ -59,7 +52,7 @@ class OnlyTeam implements Filter ]; if ($this->aclManager->getPermissionLevel($this->user, 'portalPermission') === Table::LEVEL_YES) { - $orGroup['type'] = 'portal'; + $orGroup['type'] = User::TYPE_PORTAL; } $queryBuilder diff --git a/application/Espo/Core/Select/AccessControl/Filters/All.php b/application/Espo/Core/Select/AccessControl/Filters/All.php index 50e11b9bb2..272cfa91df 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/All.php +++ b/application/Espo/Core/Select/AccessControl/Filters/All.php @@ -29,14 +29,11 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class All implements Filter { public function apply(QueryBuilder $queryBuilder): void - { - } + {} } diff --git a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php index 3637244557..04e021fe5f 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php +++ b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php @@ -39,18 +39,12 @@ use LogicException; class ForeignOnlyOwn implements Filter { - private string $entityType; - private User $user; - private Metadata $metadata; - private Defs $defs; - - public function __construct(string $entityType, User $user, Metadata $metadata, Defs $defs) - { - $this->user = $user; - $this->entityType = $entityType; - $this->metadata = $metadata; - $this->defs = $defs; - } + public function __construct( + private string $entityType, + private User $user, + private Metadata $metadata, + private Defs $defs + ) {} public function apply(SelectBuilder $queryBuilder): void { diff --git a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php index afcc1f8807..7b3e642c98 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php +++ b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php @@ -72,7 +72,7 @@ class ForeignOnlyTeam implements Filter return; } - $teamIdList = $this->user->getLinkMultipleIdList('teams') ?? []; + $teamIdList = $this->user->getTeamIdList(); if (count($teamIdList) === 0) { $queryBuilder->where([ diff --git a/application/Espo/Core/Select/AccessControl/Filters/Mandatory.php b/application/Espo/Core/Select/AccessControl/Filters/Mandatory.php index a5e814e096..a6c67a0f34 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/Mandatory.php +++ b/application/Espo/Core/Select/AccessControl/Filters/Mandatory.php @@ -29,15 +29,11 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class Mandatory implements Filter { public function apply(QueryBuilder $queryBuilder): void - { - - } + {} } diff --git a/application/Espo/Core/Select/AccessControl/Filters/No.php b/application/Espo/Core/Select/AccessControl/Filters/No.php index db2e5492fd..d0728926de 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/No.php +++ b/application/Espo/Core/Select/AccessControl/Filters/No.php @@ -29,17 +29,13 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class No implements Filter { public function apply(QueryBuilder $queryBuilder): void { - $queryBuilder->where([ - 'id' => null, - ]); + $queryBuilder->where(['id' => null]); } } diff --git a/application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php b/application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php index 9aeccee0b9..8a3597b67d 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php +++ b/application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php @@ -29,53 +29,55 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, - Core\Select\Helpers\FieldHelper, - Entities\User, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\Core\Select\Helpers\FieldHelper; +use Espo\Entities\User; +use Espo\ORM\Defs; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class OnlyOwn implements Filter { - private $user; - - private $fieldHelper; - - public function __construct(User $user, FieldHelper $fieldHelper) - { - $this->user = $user; - $this->fieldHelper = $fieldHelper; - } + public function __construct( + private User $user, + private FieldHelper $fieldHelper, + private string $entityType, + private Defs $defs + ) {} public function apply(QueryBuilder $queryBuilder): void { if ($this->fieldHelper->hasAssignedUsersField()) { - $queryBuilder->distinct(); + $relationDefs = $this->defs + ->getEntity($this->entityType) + ->getRelation('assignedUsers'); - $queryBuilder->leftJoin('assignedUsers', 'assignedUsersAccess'); + $middleEntityType = ucfirst($relationDefs->getRelationshipName()); + $key1 = $relationDefs->getMidKey(); + $key2 = $relationDefs->getForeignMidKey(); - $queryBuilder->where([ - 'assignedUsersAccess.id' => $this->user->id, - ]); + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, 'assignedUsersMiddle', [ + "assignedUsersMiddle.{$key1}:" => 'id', + 'assignedUsersMiddle.deleted' => false, + ]) + ->where(["assignedUsersMiddle.{$key2}" => $this->user->getId()]) + ->build(); + + $queryBuilder->where(['id=s' => $subQuery->getRaw()]); return; } if ($this->fieldHelper->hasAssignedUserField()) { - $queryBuilder->where([ - 'assignedUserId' => $this->user->id, - ]); + $queryBuilder->where(['assignedUserId' => $this->user->getId()]); return; } if ($this->fieldHelper->hasCreatedByField()) { - $queryBuilder->where([ - 'createdById' => $this->user->id, - ]); - - return; + $queryBuilder->where(['createdById' => $this->user->getId()]); } } } diff --git a/application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php b/application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php index 18e77a0389..9526fdcc69 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php +++ b/application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php @@ -29,65 +29,68 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, - Core\Select\Helpers\FieldHelper, - Entities\User, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\Core\Select\Helpers\FieldHelper; +use Espo\Entities\Team; +use Espo\Entities\User; +use Espo\ORM\Defs; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class OnlyTeam implements Filter { - private $user; - - private $fieldHelper; - - public function __construct(User $user, FieldHelper $fieldHelper) - { - $this->user = $user; - $this->fieldHelper = $fieldHelper; - } + public function __construct( + private User $user, + private FieldHelper $fieldHelper, + private string $entityType, + private Defs $defs + ) {} public function apply(QueryBuilder $queryBuilder): void { if (!$this->fieldHelper->hasTeamsField()) { - $queryBuilder->where([ - 'id' => null, - ]); + $queryBuilder->where(['id' => null]); return; } - $queryBuilder->distinct(); + $subQueryBuilder = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin(Team::RELATIONSHIP_ENTITY_TEAM, 'entityTeam', [ + 'entityTeam.entityId:' => 'id', + 'entityTeam.entityType' => $this->entityType, + 'entityTeam.deleted' => false, + ]); - $queryBuilder->leftJoin('teams', 'teamsAccess'); + $orGroup = ['entityTeam.teamId' => $this->user->getTeamIdList()]; if ($this->fieldHelper->hasAssignedUsersField()) { - $queryBuilder->leftJoin('assignedUsers', 'assignedUsersAccess'); + $relationDefs = $this->defs + ->getEntity($this->entityType) + ->getRelation('assignedUsers'); - $queryBuilder->where([ - 'OR' => [ - 'teamsAccess.id' => $this->user->getTeamIdList(), - 'assignedUsersAccess.id' => $this->user->id, - ] + $middleEntityType = ucfirst($relationDefs->getRelationshipName()); + $key1 = $relationDefs->getMidKey(); + $key2 = $relationDefs->getForeignMidKey(); + + $subQueryBuilder->leftJoin($middleEntityType, 'assignedUsersMiddle', [ + "assignedUsersMiddle.{$key1}:" => 'id', + 'assignedUsersMiddle.deleted' => false, ]); - return; + $orGroup["assignedUsersMiddle.{$key2}"] = $this->user->getId(); } - - $orGroup = [ - 'teamsAccess.id' => $this->user->getTeamIdList(), - ]; - - if ($this->fieldHelper->hasAssignedUserField()) { - $orGroup['assignedUserId'] = $this->user->id; + else if ($this->fieldHelper->hasAssignedUserField()) { + $orGroup['assignedUserId'] = $this->user->getId(); } else if ($this->fieldHelper->hasCreatedByField()) { - $orGroup['createdById'] = $this->user->id; + $orGroup['createdById'] = $this->user->getId(); } - $queryBuilder->where([ - 'OR' => $orGroup, - ]); + $subQuery = $subQueryBuilder + ->where(['OR' => $orGroup]) + ->build(); + + $queryBuilder->where(['id=s' => $subQuery->getRaw()]); } } diff --git a/application/Espo/Core/Select/AccessControl/Filters/PortalAll.php b/application/Espo/Core/Select/AccessControl/Filters/PortalAll.php index fc9137cb08..cddf3649f4 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/PortalAll.php +++ b/application/Espo/Core/Select/AccessControl/Filters/PortalAll.php @@ -29,15 +29,11 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class PortalAll implements Filter { public function apply(QueryBuilder $queryBuilder): void - { - - } + {} } diff --git a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyAccount.php b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyAccount.php index e1bfbbfc8f..965174660b 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyAccount.php +++ b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyAccount.php @@ -29,24 +29,19 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, - Core\Select\Helpers\FieldHelper, - Entities\User, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\Core\Select\Helpers\FieldHelper; +use Espo\Entities\User; +use Espo\Modules\Crm\Entities\Account; +use Espo\Modules\Crm\Entities\Contact; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class PortalOnlyAccount implements Filter { - private $user; - - private $fieldHelper; - - public function __construct(User $user, FieldHelper $fieldHelper) - { - $this->user = $user; - $this->fieldHelper = $fieldHelper; - } + public function __construct( + private User $user, + private FieldHelper $fieldHelper + ) {} public function apply(QueryBuilder $queryBuilder): void { @@ -70,13 +65,13 @@ class PortalOnlyAccount implements Filter if ($this->fieldHelper->hasParentField()) { $orGroup[] = [ - 'parentType' => 'Account', + 'parentType' => Account::ENTITY_TYPE, 'parentId' => $accountIdList, ]; if ($contactId) { $orGroup[] = [ - 'parentType' => 'Contact', + 'parentType' => Contact::ENTITY_TYPE, 'parentId' => $contactId, ]; } @@ -98,19 +93,15 @@ class PortalOnlyAccount implements Filter } if ($this->fieldHelper->hasCreatedByField()) { - $orGroup['createdById'] = $this->user->id; + $orGroup['createdById'] = $this->user->getId(); } if (empty($orGroup)) { - $queryBuilder->where([ - 'id' => null, - ]); + $queryBuilder->where(['id' => null]); return; } - $queryBuilder->where([ - 'OR' => $orGroup, - ]); + $queryBuilder->where(['OR' => $orGroup]); } } diff --git a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyContact.php b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyContact.php index 2a52b5aa37..cf54cdb312 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyContact.php +++ b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyContact.php @@ -29,24 +29,18 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, - Core\Select\Helpers\FieldHelper, - Entities\User, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\Core\Select\Helpers\FieldHelper; +use Espo\Entities\User; +use Espo\Modules\Crm\Entities\Contact; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class PortalOnlyContact implements Filter { - private $user; - - private $fieldHelper; - - public function __construct(User $user, FieldHelper $fieldHelper) - { - $this->user = $user; - $this->fieldHelper = $fieldHelper; - } + public function __construct( + private User $user, + private FieldHelper $fieldHelper + ) {} public function apply(QueryBuilder $queryBuilder): void { @@ -69,26 +63,22 @@ class PortalOnlyContact implements Filter if ($this->fieldHelper->hasParentField()) { $orGroup[] = [ - 'parentType' => 'Contact', + 'parentType' => Contact::ENTITY_TYPE, 'parentId' => $contactId, ]; } } if ($this->fieldHelper->hasCreatedByField()) { - $orGroup['createdById'] = $this->user->id; + $orGroup['createdById'] = $this->user->getId(); } if (empty($orGroup)) { - $queryBuilder->where([ - 'id' => null, - ]); + $queryBuilder->where(['id' => null]); return; } - $queryBuilder->where([ - 'OR' => $orGroup, - ]); + $queryBuilder->where(['OR' => $orGroup]); } } diff --git a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyOwn.php b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyOwn.php index 4a179b5b78..8261485ede 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyOwn.php +++ b/application/Espo/Core/Select/AccessControl/Filters/PortalOnlyOwn.php @@ -29,24 +29,15 @@ namespace Espo\Core\Select\AccessControl\Filters; -use Espo\{ - ORM\Query\SelectBuilder as QueryBuilder, - Core\Select\AccessControl\Filter, - Core\Select\Helpers\FieldHelper, - Entities\User, -}; +use Espo\Core\Select\AccessControl\Filter; +use Espo\Core\Select\Helpers\FieldHelper; +use Espo\Entities\User; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; class PortalOnlyOwn implements Filter { - private $user; - - private $fieldHelper; - - public function __construct(User $user, FieldHelper $fieldHelper) - { - $this->user = $user; - $this->fieldHelper = $fieldHelper; - } + public function __construct(private User $user, private FieldHelper $fieldHelper) + {} public function apply(QueryBuilder $queryBuilder): void { @@ -58,8 +49,6 @@ class PortalOnlyOwn implements Filter return; } - $queryBuilder->where([ - 'id' => null, - ]); + $queryBuilder->where(['id' => null]); } } diff --git a/application/Espo/Core/Select/Where/Converter.php b/application/Espo/Core/Select/Where/Converter.php index 289e87af9c..99ce1cba26 100644 --- a/application/Espo/Core/Select/Where/Converter.php +++ b/application/Espo/Core/Select/Where/Converter.php @@ -29,45 +29,35 @@ namespace Espo\Core\Select\Where; -use Espo\{ - Core\Exceptions\Error, - ORM\Query\SelectBuilder as QueryBuilder, - ORM\Query\Part\WhereClause, - ORM\Query\Part\WhereItem, - ORM\Entity, - ORM\Defs as ORMDefs, - Core\Select\Helpers\RandomStringGenerator, -}; +use Espo\Core\Exceptions\Error; +use Espo\Core\Select\Helpers\RandomStringGenerator; +use Espo\Entities\Team; +use Espo\Entities\User; +use Espo\ORM\Defs as ORMDefs; +use Espo\ORM\Entity; +use Espo\ORM\Query\Part\WhereClause; +use Espo\ORM\Query\Part\WhereItem; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; /** * Converts a search where (passed from front-end) to a where clause (for ORM). */ class Converter { - private string $entityType; - - private ItemConverter $itemConverter; - - private Scanner $scanner; - - private RandomStringGenerator $randomStringGenerator; - - private ORMDefs $ormDefs; + private const TYPE_IN_CATEGORY = 'inCategory'; + private const TYPE_IS_USER_FROM_TEAMS = 'isUserFromTeams'; public function __construct( - string $entityType, - ItemConverter $itemConverter, - Scanner $scanner, - RandomStringGenerator $randomStringGenerator, - ORMDefs $ormDefs - ) { - $this->entityType = $entityType; - $this->itemConverter = $itemConverter; - $this->scanner = $scanner; - $this->randomStringGenerator = $randomStringGenerator; - $this->ormDefs = $ormDefs; - } + private string $entityType, + private ItemConverter $itemConverter, + private Scanner $scanner, + private RandomStringGenerator $randomStringGenerator, + private ORMDefs $ormDefs + ) {} + /** + * @throws Error + */ public function convert(QueryBuilder $queryBuilder, Item $item): WhereItem { $whereClause = []; @@ -90,7 +80,7 @@ class Converter } /** - * @return array + * @return array * @throws Error */ private function itemToList(Item $item): array @@ -111,7 +101,7 @@ class Converter } /** - * @return ?array + * @return ?array * @throws Error */ private function processItem(QueryBuilder $queryBuilder, Item $item): ?array @@ -120,9 +110,10 @@ class Converter $attribute = $item->getAttribute(); $value = $item->getValue(); - $methodName = 'apply' . ucfirst($type); - - if (method_exists($this, $methodName)) { + if ( + $type === self::TYPE_IN_CATEGORY || + $type === self::TYPE_IS_USER_FROM_TEAMS + ) { // Processing special filters. Only at the top level of the tree. if (!$attribute) { @@ -133,7 +124,11 @@ class Converter return null; } - return $this->$methodName($queryBuilder, $attribute, $value); + if ($type === self::TYPE_IN_CATEGORY) { + return $this->applyInCategory($queryBuilder, $attribute, $value); + } + + return $this->applyIsUserFromTeams($queryBuilder, $attribute, $value); } return $this->itemConverter->convert($queryBuilder, $item)->getRaw(); @@ -141,7 +136,7 @@ class Converter /** * @param mixed $value - * @return array + * @return array * @throws Error */ private function applyInCategory(QueryBuilder $queryBuilder, string $attribute, $value): array @@ -207,7 +202,7 @@ class Converter /** * @param mixed $value - * @return array + * @return array * @throws Error */ private function applyIsUserFromTeams(QueryBuilder $queryBuilder, string $attribute, $value): array @@ -229,7 +224,7 @@ class Converter $relationType = $defs->getType(); $entityType = $defs->getForeignEntityType(); - if ($entityType !== 'User') { + if ($entityType !== User::ENTITY_TYPE) { throw new Error("Not supported link '{$link}' in where item."); } @@ -239,7 +234,7 @@ class Converter $aliasName = $link . 'IsUserFromTeamsFilter' . $this->randomStringGenerator->generate(); $queryBuilder->leftJoin( - 'TeamUser', + Team::RELATIONSHIP_TEAM_USER, $aliasName . 'Middle', [ $aliasName . 'Middle.userId:' => $key, diff --git a/application/Espo/Core/Select/Where/ItemGeneralConverter.php b/application/Espo/Core/Select/Where/ItemGeneralConverter.php index 291a8419af..a4c33d43ee 100644 --- a/application/Espo/Core/Select/Where/ItemGeneralConverter.php +++ b/application/Espo/Core/Select/Where/ItemGeneralConverter.php @@ -30,20 +30,17 @@ namespace Espo\Core\Select\Where; use Espo\Core\Select\Where\Item\Type; - -use Espo\{ - Core\Exceptions\Error, - ORM\Query\SelectBuilder as QueryBuilder, - ORM\Query\Part\WhereClause, - ORM\Query\Part\WhereItem as WhereClauseItem, - ORM\EntityManager, - ORM\Entity, - ORM\Defs as ORMDefs, - Entities\User, - Core\Utils\Config, - Core\Select\Helpers\RandomStringGenerator, - Core\Utils\Metadata, -}; +use Espo\Core\Exceptions\Error; +use Espo\Core\Select\Helpers\RandomStringGenerator; +use Espo\Core\Utils\Config; +use Espo\Core\Utils\Metadata; +use Espo\Entities\ArrayValue; +use Espo\Entities\User; +use Espo\ORM\Defs as ORMDefs; +use Espo\ORM\Entity; +use Espo\ORM\Query\Part\WhereClause; +use Espo\ORM\Query\Part\WhereItem as WhereClauseItem; +use Espo\ORM\Query\SelectBuilder as QueryBuilder; use DateTime; use DateInterval; @@ -53,50 +50,21 @@ use DateInterval; */ class ItemGeneralConverter implements ItemConverter { - private string $entityType; - - private User $user; - - private DateTimeItemTransformer $dateTimeItemTransformer; - - private Scanner $scanner; - - private ItemConverterFactory $itemConverterFactory; - - private RandomStringGenerator $randomStringGenerator; - - private EntityManager $entityManager; - - private ORMDefs $ormDefs; - - private Config $config; - - private Metadata $metadata; - public function __construct( - string $entityType, - User $user, - DateTimeItemTransformer $dateTimeItemTransformer, - Scanner $scanner, - ItemConverterFactory $itemConverterFactory, - RandomStringGenerator $randomStringGenerator, - EntityManager $entityManager, - ORMDefs $ormDefs, - Config $config, - Metadata $metadata - ) { - $this->entityType = $entityType; - $this->user = $user; - $this->dateTimeItemTransformer = $dateTimeItemTransformer; - $this->scanner = $scanner; - $this->itemConverterFactory = $itemConverterFactory; - $this->randomStringGenerator = $randomStringGenerator; - $this->entityManager = $entityManager; - $this->ormDefs = $ormDefs; - $this->config = $config; - $this->metadata = $metadata; - } + private string $entityType, + private User $user, + private DateTimeItemTransformer $dateTimeItemTransformer, + private Scanner $scanner, + private ItemConverterFactory $itemConverterFactory, + private RandomStringGenerator $randomStringGenerator, + private ORMDefs $ormDefs, + private Config $config, + private Metadata $metadata + ) {} + /** + * @throws Error + */ public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem { $type = $item->getType(); @@ -133,18 +101,12 @@ class ItemGeneralConverter implements ItemConverter switch ($type) { case Type::OR: case Type::AND: - - return WhereClause::fromRaw( - $this->groupProcessAndOr($queryBuilder, $type, $attribute, $value) - ); + return WhereClause::fromRaw($this->groupProcessAndOr($queryBuilder, $type, $attribute, $value)); case Type::NOT: case Type::SUBQUERY_NOT_IN: case Type::SUBQUERY_IN: - - return WhereClause::fromRaw( - $this->groupProcessSubQuery($queryBuilder, $type, $attribute, $value) - ); + return WhereClause::fromRaw($this->groupProcessSubQuery($queryBuilder, $type, $attribute, $value)); } if (!$attribute) { @@ -152,26 +114,61 @@ class ItemGeneralConverter implements ItemConverter } switch ($type) { + // Revise. case 'columnLike': case 'columnIn': case 'columnNotIn': case 'columnIsNotNull': case 'columnEquals': case 'columnNotEquals': - - return WhereClause::fromRaw( - $this->groupProcessColumn($queryBuilder, $type, $attribute, $value) - ); + return WhereClause::fromRaw($this->groupProcessColumn($queryBuilder, $type, $attribute, $value)); case Type::ARRAY_ANY_OF: case Type::ARRAY_NONE_OF: case Type::ARRAY_IS_EMPTY: - case Type::ARRAY_IS_EMPTY: case Type::ARRAY_ALL_OF: + case Type::ARRAY_IS_NOT_EMPTY: + return WhereClause::fromRaw($this->groupProcessArray($queryBuilder, $type, $attribute, $value)); + } - return WhereClause::fromRaw( - $this->groupProcessArray($queryBuilder, $type, $attribute, $value) - ); + if ($type === Type::IS_LINKED_WITH) { + return WhereClause::fromRaw($this->processLinkedWith($queryBuilder, $attribute, $value)); + } + + if ($type === Type::IS_NOT_LINKED_WITH) { + return WhereClause::fromRaw($this->processNotLinkedWith($queryBuilder, $attribute, $value)); + } + + if ($type === Type::IS_LINKED_WITH_ALL) { + return WhereClause::fromRaw($this->processLinkedWithAll($queryBuilder, $attribute, $value)); + } + + if ($type === Type::IS_LINKED_WITH_ANY) { + return WhereClause::fromRaw($this->processIsLinked($queryBuilder, $attribute)); + } + + if ($type === Type::IS_LINKED_WITH_NONE) { + return WhereClause::fromRaw($this->processIsNotLinked($queryBuilder, $attribute)); + } + + if ($type === Type::EXPRESSION) { + return WhereClause::fromRaw($this->processExpression($queryBuilder, $attribute, $value)); + } + + if ($type === Type::EQUALS) { + return WhereClause::fromRaw($this->processEquals($queryBuilder, $attribute, $value)); + } + + if ($type === Type::NOT_EQUALS) { + return WhereClause::fromRaw($this->processNotEquals($queryBuilder, $attribute, $value)); + } + + if ($type === Type::ON) { + return WhereClause::fromRaw($this->processOn($queryBuilder, $attribute, $value)); + } + + if ($type === Type::NOT_ON) { + return WhereClause::fromRaw($this->processNotOn($queryBuilder, $attribute, $value)); } $methodName = 'process' . ucfirst($type); @@ -193,10 +190,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function groupProcessAndOr( + private function groupProcessAndOr( QueryBuilder $queryBuilder, string $type, ?string $attribute, @@ -228,10 +225,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function groupProcessSubQuery( + private function groupProcessSubQuery( QueryBuilder $queryBuilder, string $type, ?string $attribute, @@ -242,9 +239,7 @@ class ItemGeneralConverter implements ItemConverter throw new Error("Bad where item."); } - $sqQueryBuilder = $this->entityManager - ->getQueryBuilder() - ->select() + $sqQueryBuilder = QueryBuilder::create() ->from($this->entityType); $whereItem = Item::fromRaw([ @@ -258,7 +253,7 @@ class ItemGeneralConverter implements ItemConverter $rawParams = $sqQueryBuilder->build()->getRaw(); - $key = $type === 'subQueryIn' ? 'id=s' : 'id!=s'; + $key = $type === Type::SUBQUERY_IN ? 'id=s' : 'id!=s'; return [ $key => [ @@ -273,10 +268,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function groupProcessColumn( + private function groupProcessColumn( QueryBuilder $queryBuilder, string $type, string $attribute, @@ -344,10 +339,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function groupProcessArray( + private function groupProcessArray( QueryBuilder $queryBuilder, string $type, string $attribute, @@ -360,7 +355,7 @@ class ItemGeneralConverter implements ItemConverter $arrayEntityType = $this->entityType; $idPart = 'id'; - $isForeign = strpos($attribute, '.') !== false; + $isForeign = str_contains($attribute, '.'); $isForeignType = false; @@ -368,7 +363,6 @@ class ItemGeneralConverter implements ItemConverter if (!$isForeign) { $isForeignType = $entityDefs->getAttribute($attribute)->getType() === Entity::FOREIGN; - $isForeign = $isForeignType; } @@ -378,7 +372,7 @@ class ItemGeneralConverter implements ItemConverter $arrayAttribute = $entityDefs->getAttribute($attribute)->getParam('foreign'); } else { - list($arrayAttributeLink, $arrayAttribute) = explode('.', $attribute); + [$arrayAttributeLink, $arrayAttribute] = explode('.', $attribute); } if (!$arrayAttributeLink || !$arrayAttribute) { @@ -386,103 +380,88 @@ class ItemGeneralConverter implements ItemConverter } $arrayEntityType = $entityDefs->getRelation($arrayAttributeLink)->getForeignEntityType(); - $arrayLinkAlias = $arrayAttributeLink . 'ArrayFilter' . $this->randomStringGenerator->generate(); - $idPart = $arrayLinkAlias . '.id'; $queryBuilder->leftJoin($arrayAttributeLink, $arrayLinkAlias); $relationType = $entityDefs->getRelation($arrayAttributeLink)->getType(); - if ($relationType === Entity::MANY_MANY || $relationType === Entity::HAS_MANY) { + if ( + $relationType === Entity::MANY_MANY || + $relationType === Entity::HAS_MANY + ) { $queryBuilder->distinct(); } } if ($type === Type::ARRAY_ANY_OF) { - if (is_null($value) || !$value && !is_array($value)) { + if (!$value && !is_array($value)) { throw new Error("Bad where item. No value."); } - $queryBuilder->leftJoin( - 'ArrayValue', - $arrayValueAlias, - [ - $arrayValueAlias . '.entityId:' => $idPart, - $arrayValueAlias . '.entityType' => $arrayEntityType, - $arrayValueAlias . '.attribute' => $arrayAttribute, - ] - ); + $subQuery = QueryBuilder::create() + ->select('entityId') + ->from(ArrayValue::ENTITY_TYPE) + ->where([ + 'entityType' => $arrayEntityType, + 'attribute' => $arrayAttribute, + 'value' => $value, + ]) + ->build(); - $queryBuilder->distinct(); - - return [ - $arrayValueAlias . '.value' => $value, - ]; + return [$idPart . '=s' => $subQuery->getRaw()]; } if ($type === Type::ARRAY_NONE_OF) { - if (is_null($value) || !$value && !is_array($value)) { + if (!$value && !is_array($value)) { throw new Error("Bad where item 'array'. No value."); } - $queryBuilder->leftJoin( - 'ArrayValue', - $arrayValueAlias, - [ - $arrayValueAlias . '.entityId:' => $idPart, - $arrayValueAlias . '.entityType' => $arrayEntityType, - $arrayValueAlias . '.attribute' => $arrayAttribute, - $arrayValueAlias . '.value=' => $value, - ] - ); + $subQuery = QueryBuilder::create() + ->select('entityId') + ->from(ArrayValue::ENTITY_TYPE) + ->where([ + 'entityType' => $arrayEntityType, + 'attribute' => $arrayAttribute, + 'value' => $value, + ]) + ->build(); - $queryBuilder->distinct(); - - return [ - $arrayValueAlias . '.id' => null, - ]; + return [$idPart . '!=s' => $subQuery->getRaw()]; } if ($type === Type::ARRAY_IS_EMPTY) { - $queryBuilder->distinct(); + // Though distinct-left-join may perform faster than not-in-subquery + // it's reasonable to avoid using distinct as it may negatively affect + // performance when other filters are applied. + $subQuery = QueryBuilder::create() + ->select('entityId') + ->from(ArrayValue::ENTITY_TYPE) + ->where([ + 'entityType' => $arrayEntityType, + 'attribute' => $arrayAttribute, + ]) + ->build(); - $queryBuilder->leftJoin( - 'ArrayValue', - $arrayValueAlias, - [ - $arrayValueAlias . '.entityId:' => $idPart, - $arrayValueAlias . '.entityType' => $arrayEntityType, - $arrayValueAlias . '.attribute' => $arrayAttribute, - ] - ); - - return [ - $arrayValueAlias . '.id' => null, - ]; + return [$idPart . '!=s' => $subQuery->getRaw()]; } if ($type === Type::ARRAY_IS_NOT_EMPTY) { - $queryBuilder->distinct(); + $subQuery = QueryBuilder::create() + ->select('entityId') + ->from(ArrayValue::ENTITY_TYPE) + ->where([ + 'entityType' => $arrayEntityType, + 'attribute' => $arrayAttribute, + ]) + ->build(); - $queryBuilder->leftJoin( - 'ArrayValue', - $arrayValueAlias, - [ - $arrayValueAlias . '.entityId:' => $idPart, - $arrayValueAlias . '.entityType' => $arrayEntityType, - $arrayValueAlias . '.attribute' => $arrayAttribute, - ] - ); - - return [ - $arrayValueAlias . '.id!=' => null, - ]; + return [$idPart . '=s' => $subQuery->getRaw()]; } if ($type === Type::ARRAY_ALL_OF) { - if (is_null($value) || !$value && !is_array($value)) { + if (!$value && !is_array($value)) { throw new Error("Bad where item 'array'. No value."); } @@ -495,7 +474,7 @@ class ItemGeneralConverter implements ItemConverter foreach ($value as $arrayValue) { $whereList[] = [ $idPart .'=s' => QueryBuilder::create() - ->from('ArrayValue') + ->from(ArrayValue::ENTITY_TYPE) ->select('entityId') ->where([ 'value' => $arrayValue, @@ -518,13 +497,13 @@ class ItemGeneralConverter implements ItemConverter * A complex expression w/o a value. * * @param mixed $value - * @return array + * @return array */ - protected function processExpression(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processExpression(QueryBuilder $queryBuilder, string $attribute, $value): array { $key = $attribute; - if (substr($key, -1) !== ':') { + if (!str_ends_with($key, ':')) { $key .= ':'; } @@ -535,9 +514,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLike(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLike(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '*' => $value, @@ -546,9 +525,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNotLike(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotLike(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '!*' => $value, @@ -557,9 +536,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processEquals(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processEquals(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '=' => $value, @@ -568,18 +547,18 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processOn(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processOn(QueryBuilder $queryBuilder, string $attribute, $value): array { return $this->processEquals($queryBuilder, $attribute, $value); } /** * @param mixed $value - * @return array + * @return array */ - protected function processNotEquals(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotEquals(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '!=' => $value, @@ -588,18 +567,18 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNotOn(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotOn(QueryBuilder $queryBuilder, string $attribute, $value): array { return $this->processNotEquals($queryBuilder, $attribute, $value); } /** * @param mixed $value - * @return array + * @return array */ - protected function processStartsWith(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processStartsWith(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '*' => $value . '%', @@ -608,9 +587,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processEndsWith(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processEndsWith(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '*' => '%' . $value, @@ -619,9 +598,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processContains(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processContains(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '*' => '%' . $value . '%', @@ -630,9 +609,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNotContains(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotContains(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '!*' => '%' . $value . '%', @@ -641,9 +620,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processGreaterThan(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processGreaterThan(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '>' => $value, @@ -652,18 +631,18 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processAfter(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processAfter(QueryBuilder $queryBuilder, string $attribute, $value): array { return $this->processGreaterThan($queryBuilder, $attribute, $value); } /** * @param mixed $value - * @return array + * @return array */ - protected function processLessThan(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLessThan(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '<' => $value, @@ -672,18 +651,18 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processBefore(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processBefore(QueryBuilder $queryBuilder, string $attribute, $value): array { return $this->processLessThan($queryBuilder, $attribute, $value); } /** * @param mixed $value - * @return array + * @return array */ - protected function processGreaterThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processGreaterThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '>=' => $value, @@ -692,9 +671,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLessThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLessThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '<=' => $value, @@ -703,10 +682,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processIn(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIn(QueryBuilder $queryBuilder, string $attribute, $value): array { if (!is_array($value)) { throw new Error("Bad where item 'in'."); @@ -719,10 +698,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processNotIn(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotIn(QueryBuilder $queryBuilder, string $attribute, $value): array { if (!is_array($value)) { throw new Error("Bad where item 'notIn'."); @@ -735,10 +714,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processBetween(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processBetween(QueryBuilder $queryBuilder, string $attribute, $value): array { if (!is_array($value) || count($value) < 2) { throw new Error("Bad where item 'between'."); @@ -754,9 +733,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processAny(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processAny(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ 'true:' => null, @@ -765,9 +744,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNone(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNone(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ 'false:' => null, @@ -776,9 +755,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processIsNull(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIsNull(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '=' => null, @@ -787,9 +766,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processIsNotNull(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIsNotNull(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '!=' => null, @@ -798,18 +777,18 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processEver(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processEver(QueryBuilder $queryBuilder, string $attribute, $value): array { return $this->processIsNotNull($queryBuilder, $attribute, $value); } /** * @param mixed $value - * @return array + * @return array */ - protected function processIsTrue(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIsTrue(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '=' => true, @@ -818,9 +797,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processIsFalse(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIsFalse(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '=' => false, @@ -829,9 +808,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processToday(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processToday(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '=' => date('Y-m-d'), @@ -840,9 +819,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processPast(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processPast(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '<' => date('Y-m-d'), @@ -851,9 +830,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processFuture(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processFuture(QueryBuilder $queryBuilder, string $attribute, $value): array { return [ $attribute . '>' => date('Y-m-d'), @@ -862,9 +841,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLastSevenDays(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastSevenDays(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt1 = new DateTime(); @@ -882,9 +861,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLastXDays(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastXDays(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt1 = new DateTime(); @@ -904,9 +883,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNextXDays(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNextXDays(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt1 = new DateTime(); @@ -926,9 +905,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processOlderThanXDays(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processOlderThanXDays(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -943,9 +922,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processAfterXDays(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processAfterXDays(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -960,9 +939,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processCurrentMonth(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processCurrentMonth(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -976,9 +955,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLastMonth(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastMonth(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -992,9 +971,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processNextMonth(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNextMonth(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -1008,10 +987,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws \Exception */ - protected function processCurrentQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processCurrentQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -1029,10 +1008,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws \Exception */ - protected function processLastQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -1057,9 +1036,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processCurrentYear(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processCurrentYear(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -1073,9 +1052,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLastYear(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastYear(QueryBuilder $queryBuilder, string $attribute, $value): array { $dt = new DateTime(); @@ -1089,9 +1068,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processCurrentFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processCurrentFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array { $dtToday = new DateTime(); $dt = new DateTime(); @@ -1114,9 +1093,9 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array */ - protected function processLastFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array { $dtToday = new DateTime(); $dt = new DateTime(); @@ -1141,10 +1120,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws \Exception */ - protected function processCurrentFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processCurrentFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array { $dtToday = new DateTime(); $dt = new DateTime(); @@ -1176,10 +1155,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws \Exception */ - protected function processLastFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLastFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array { $dtToday = new DateTime(); $dt = new DateTime(); @@ -1212,44 +1191,113 @@ class ItemGeneralConverter implements ItemConverter } /** - * @param mixed $value - * @return array + * @return array */ - protected function processIsNotLinked(QueryBuilder $queryBuilder, string $attribute, $value): array - { - return [ - 'id!=s' => [ - 'select' => ['id'], - 'from' => $this->entityType, - 'joins' => [$attribute], - ] - ]; - } - - /** - * @param mixed $value - * @return array - */ - protected function processIsLinked(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processIsNotLinked(QueryBuilder $queryBuilder, string $attribute): array { $link = $attribute; - $alias = $link . 'IsLinkedFilter' . $this->randomStringGenerator->generate(); - $queryBuilder->distinct(); - $queryBuilder->leftJoin($link, $alias); + $defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link); - return [ - $alias . '.id!=' => null, - ]; + $key = $defs->getForeignMidKey(); + $nearKey = $defs->getMidKey(); + $middleEntityType = ucfirst($defs->getRelationshipName()); + + $relationType = $defs->getType(); + + if ($relationType == Entity::MANY_MANY) { + // The foreign table is not joined as it would perform much slower. + // Trade off is that if a foreign record is deleted but the middle table + // is not yet deleted, it will give a non-actual result. + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, $alias, [ + "{$alias}.{$nearKey}:" => 'id', + "{$alias}.deleted" => 0, + ]) + ->where(["{$alias}.{$key}" => null]) + ->build(); + + return ['id=s' => $subQuery->getRaw()]; + } + + if ( + $relationType == Entity::HAS_MANY || + $relationType == Entity::HAS_ONE || + $relationType == Entity::BELONGS_TO + ) { + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($link, $alias) + ->where([$alias . '.id' => null]) + ->build(); + + return ['id=s' => $subQuery->getRaw()]; + } + + throw new Error("Bad where item. Not supported relation type."); + } + + /** + * @return array + */ + private function processIsLinked(QueryBuilder $queryBuilder, string $attribute): array + { + $link = $attribute; + $alias = $link . 'IsLinkedFilter' . $this->randomStringGenerator->generate(); + + $defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link); + + $key = $defs->getForeignMidKey(); + $nearKey = $defs->getMidKey(); + $middleEntityType = ucfirst($defs->getRelationshipName()); + + $relationType = $defs->getType(); + + if ($relationType == Entity::MANY_MANY) { + // The foreign table is not joined as it would perform much slower. + // Trade off is that if a foreign record is deleted but the middle table + // is not yet deleted, it will give a non-actual result. + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, $alias, [ + "{$alias}.{$nearKey}:" => 'id', + "{$alias}.deleted" => 0, + ]) + ->where(["{$alias}.{$key}!=" => null]) + ->build(); + + return ['id=s' => $subQuery->getRaw()]; + } + + if ( + $relationType == Entity::HAS_MANY || + $relationType == Entity::HAS_ONE || + $relationType == Entity::BELONGS_TO + ) { + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($link, $alias) + ->where([$alias . '.id!=' => null]) + ->build(); + + return ['id=s' => $subQuery->getRaw()]; + } + + throw new Error("Bad where item. Not supported relation type."); } /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array { $link = $attribute; @@ -1261,51 +1309,52 @@ class ItemGeneralConverter implements ItemConverter $alias = $link . 'LinkedWithFilter' . $this->randomStringGenerator->generate(); - if (is_null($value) || !$value && !is_array($value)) { + if (!$value && !is_array($value)) { throw new Error("Bad where item. Empty value."); } + // @todo Add check for foreign record existence. + $relationType = $defs->getType(); - $queryBuilder->distinct(); - if ($relationType == Entity::MANY_MANY) { - $queryBuilder->leftJoin($link, $alias); - $key = $defs->getForeignMidKey(); + $nearKey = $defs->getMidKey(); + $middleEntityType = ucfirst($defs->getRelationshipName()); - if (!$key) { - throw new Error("Bad link '{$link}' in where item."); - } + // Left-join performs faster than Inner-join. + // Not joining a foreign table as it affects performance in MySQL. + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, $alias, [ + "{$alias}.{$nearKey}:" => 'id', + "{$alias}.deleted" => 0, + ]) + ->where(["{$alias}.{$key}" => $value]) + ->build(); - return [ - $alias . 'Middle.' . $key => $value, - ]; + return ['id=s' => $subQuery->getRaw()]; } - else if ($relationType == Entity::HAS_MANY) { - $queryBuilder->leftJoin($link, $alias); - return [ - $alias . '.id' => $value, - ]; + if ( + $relationType == Entity::HAS_MANY || + $relationType == Entity::HAS_ONE + ) { + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($link, $alias) + ->where([$alias . '.id' => $value]) + ->build(); + + return ['id=s' => $subQuery->getRaw()]; } - else if ($relationType == Entity::BELONGS_TO) { + + if ($relationType == Entity::BELONGS_TO) { $key = $defs->getKey(); - if (!$key) { - throw new Error("Bad link '{$link}' in where item."); - } - - return [ - $key => $value, - ]; - } - else if ($relationType == Entity::HAS_ONE) { - $queryBuilder->leftJoin($link, $alias); - - return [ - $alias . '.id' => $value, - ]; + return [$key => $value]; } throw new Error("Bad where item. Not supported relation type."); @@ -1313,10 +1362,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processNotLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processNotLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array { $link = $attribute; @@ -1334,53 +1383,42 @@ class ItemGeneralConverter implements ItemConverter $relationType = $defs->getType(); - $queryBuilder->distinct(); - if ($relationType == Entity::MANY_MANY) { $key = $defs->getForeignMidKey(); + $nearKey = $defs->getMidKey(); + $middleEntityType = ucfirst($defs->getRelationshipName()); - if (!$key) { - throw new Error("Bad link '{$link}' in where item."); - } + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, $alias, [ + "{$alias}.{$nearKey}:" => 'id', + "{$alias}.deleted" => 0, + ]) + ->where(["{$alias}.{$key}=" => $value]) + ->build(); - $queryBuilder->leftJoin( - $link, - $alias, - [$key => $value] - ); - - return [ - $alias . 'Middle.' . $key => null, - ]; + return ['id!=s' => $subQuery->getRaw()]; } - else if ($relationType == Entity::HAS_MANY) { - $queryBuilder->leftJoin( - $link, - $alias, - ['id' => $value] - ); - return [ - $alias . '.id' => null, - ]; + if ( + $relationType == Entity::HAS_MANY || + $relationType == Entity::HAS_ONE + ) { + $subQuery = QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($link, $alias) + ->where(["{$alias}.id" => $value]) + ->build(); + + return ['id!=s' => $subQuery->getRaw()]; } - else if ($relationType == Entity::BELONGS_TO) { + + if ($relationType == Entity::BELONGS_TO) { $key = $defs->getKey(); - if (!$key) { - throw new Error("Bad link '{$link}' in where item."); - } - - return [ - $key . '!=' => $value, - ]; - } - else if ($relationType == Entity::HAS_ONE) { - $queryBuilder->leftJoin($link, $alias); - - return [ - $alias . '.id!=' => $value, - ]; + return [$key . '!=' => $value]; } throw new Error("Bad where item. Not supported relation type."); @@ -1388,10 +1426,10 @@ class ItemGeneralConverter implements ItemConverter /** * @param mixed $value - * @return array + * @return array * @throws Error */ - protected function processLinkedWithAll(QueryBuilder $queryBuilder, string $attribute, $value): array + private function processLinkedWithAll(QueryBuilder $queryBuilder, string $attribute, $value): array { $link = $attribute; @@ -1399,7 +1437,7 @@ class ItemGeneralConverter implements ItemConverter throw new Error("Not existing link '{$link}' in where item."); } - if (is_null($value) || !$value && !is_array($value)) { + if (!$value && !is_array($value)) { throw new Error("Bad where item. Empty value."); } @@ -1440,9 +1478,7 @@ class ItemGeneralConverter implements ItemConverter ->from($this->entityType) ->select('id') ->leftJoin($link) - ->where([ - $link . '.id' => $targetId, - ]) + ->where([$link . '.id' => $targetId]) ->build(); $whereList[] = ['id=s' => $sq->getRaw()]; diff --git a/application/Espo/Core/Select/Where/Scanner.php b/application/Espo/Core/Select/Where/Scanner.php index ed42d718c7..432d901726 100644 --- a/application/Espo/Core/Select/Where/Scanner.php +++ b/application/Espo/Core/Select/Where/Scanner.php @@ -114,7 +114,7 @@ class Scanner string $entityType ): void { - if (strpos($attribute, ':') !== false) { + if (str_contains($attribute, ':')) { $argumentList = QueryComposer::getAllAttributesFromComplexExpression($attribute); foreach ($argumentList as $argument) { @@ -126,7 +126,7 @@ class Scanner $seed = $this->getSeed($entityType); - if (strpos($attribute, '.') !== false) { + if (str_contains($attribute, '.')) { list($link, $attribute) = explode('.', $attribute); if ($seed->hasRelation($link)) { diff --git a/application/Espo/Entities/Team.php b/application/Espo/Entities/Team.php index ffb239effe..5ac5aaf8ab 100644 --- a/application/Espo/Entities/Team.php +++ b/application/Espo/Entities/Team.php @@ -35,6 +35,9 @@ class Team extends \Espo\Core\ORM\Entity { public const ENTITY_TYPE = 'Team'; + public const RELATIONSHIP_ENTITY_TEAM = 'EntityTeam'; + public const RELATIONSHIP_TEAM_USER = 'TeamUser'; + public function getWorkingTimeCalendar(): ?Link { /** @var ?Link */ diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php index 84b9b402f1..ec1bbc6ece 100644 --- a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyOwn.php @@ -30,6 +30,7 @@ namespace Espo\Modules\Crm\Classes\Select\Meeting\AccessControlFilters; use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Defs; use Espo\ORM\Query\SelectBuilder; use Espo\ORM\Query\Part\Condition as Cond; @@ -37,29 +38,45 @@ use Espo\Entities\User; class OnlyOwn implements Filter { - private $user; - - public function __construct(User $user) - { - $this->user = $user; - } + public function __construct( + private User $user, + private string $entityType, + private Defs $defs + ) {} public function apply(SelectBuilder $queryBuilder): void { - $queryBuilder - ->distinct() - ->leftJoin('users', 'usersAccess') - ->where( - Cond::or( - Cond::equal( - Cond::column('usersAccessMiddle.userId'), - $this->user->getId() - ), - Cond::equal( - Cond::column('assignedUserId'), - $this->user->getId() + $relationDefs = $this->defs + ->getEntity($this->entityType) + ->getRelation('users'); + + $middleEntityType = ucfirst($relationDefs->getRelationshipName()); + $key1 = $relationDefs->getMidKey(); + + $queryBuilder->where( + Cond::in( + Cond::column('id'), + SelectBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin($middleEntityType, 'usersMiddle', [ + "usersMiddle.{$key1}:" => 'id', + 'usersMiddle.deleted' => false, + ]) + ->where( + Cond::or( + Cond::equal( + Cond::column('usersMiddle.userId'), + $this->user->getId() + ), + Cond::equal( + Cond::column('assignedUserId'), + $this->user->getId() + ) + ) ) - ) - ); + ->build() + ) + ); } } diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php index c5335cba15..420d1b6b35 100644 --- a/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/AccessControlFilters/OnlyTeam.php @@ -30,6 +30,7 @@ namespace Espo\Modules\Crm\Classes\Select\Meeting\AccessControlFilters; use Espo\Core\Select\AccessControl\Filter; +use Espo\ORM\Defs; use Espo\ORM\Query\SelectBuilder; use Espo\ORM\Query\Part\Condition as Cond; @@ -37,34 +38,54 @@ use Espo\Entities\User; class OnlyTeam implements Filter { - private $user; - - public function __construct(User $user) - { - $this->user = $user; - } + public function __construct( + private User $user, + private string $entityType, + private Defs $defs + ) {} public function apply(SelectBuilder $queryBuilder): void { - $queryBuilder - ->distinct() - ->leftJoin('teams', 'teamsAccess') - ->leftJoin('users', 'usersAccess') - ->where( - Cond::or( - Cond::in( - Cond::column('teamsAccessMiddle.teamId'), - $this->user->getLinkMultipleIdList(User::LINK_TEAMS) ?? [] - ), - Cond::equal( - Cond::column('usersAccessMiddle.userId'), - $this->user->getId() - ), - Cond::equal( - Cond::column('assignedUserId'), - $this->user->getId() + $relationDefs = $this->defs + ->getEntity($this->entityType) + ->getRelation('users'); + + $middleEntityType = ucfirst($relationDefs->getRelationshipName()); + $key1 = $relationDefs->getMidKey(); + + $queryBuilder->where( + Cond::in( + Cond::column('id'), + SelectBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin('EntityTeam', 'entityTeam', [ + 'entityTeam.entityId:' => 'id', + 'entityTeam.entityType' => $this->entityType, + 'entityTeam.deleted' => false, + ]) + ->leftJoin($middleEntityType, 'usersMiddle', [ + "usersMiddle.{$key1}:" => 'id', + 'usersMiddle.deleted' => false, + ]) + ->where( + Cond::or( + Cond::in( + Cond::column('entityTeam.teamId'), + $this->user->getTeamIdList(), + ), + Cond::equal( + Cond::column('usersMiddle.userId'), + $this->user->getId() + ), + Cond::equal( + Cond::column('assignedUserId'), + $this->user->getId() + ) + ) ) - ) - ); + ->build() + ) + ); } } diff --git a/tests/unit/Espo/Core/Select/AccessControl/FiltersTest.php b/tests/unit/Espo/Core/Select/AccessControl/FiltersTest.php index 092a2452d4..083527ed53 100644 --- a/tests/unit/Espo/Core/Select/AccessControl/FiltersTest.php +++ b/tests/unit/Espo/Core/Select/AccessControl/FiltersTest.php @@ -78,228 +78,6 @@ class FiltersTest extends \PHPUnit\Framework\TestCase $filter->apply($this->queryBuilder); } - public function testOnlyOwn1() - { - $filter = $this->createFilter(OnlyOwn::class); - - $this->initHelperMethods([ - ['hasAssignedUsersField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('distinct'); - - $this->queryBuilder - ->expects($this->once()) - ->method('leftJoin') - ->with('assignedUsers', 'assignedUsersAccess'); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'assignedUsersAccess.id' => $this->user->id, - ]); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyOwn2() - { - $filter = $this->createFilter(OnlyOwn::class); - - $this->initHelperMethods([ - ['hasAssignedUsersField', false], - ['hasAssignedUserField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'assignedUserId' => $this->user->id, - ]); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyOwn3() - { - $filter = $this->createFilter(OnlyOwn::class); - - $this->initHelperMethods([ - ['hasAssignedUsersField', false], - ['hasAssignedUserField', false], - ['hasCreatedByField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'createdById' => $this->user->id, - ]); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyTeam1() - { - $filter = $this->createFilter(OnlyTeam::class); - - $this->initHelperMethods([ - ['hasTeamsField', true], - ['hasAssignedUsersField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('distinct'); - - $this->queryBuilder - ->expects($this->exactly(2)) - ->method('leftJoin') - ->withConsecutive( - ['teams', 'teamsAccess'], - ['assignedUsers', 'assignedUsersAccess'], - ) - ->willReturn($this->queryBuilder); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'OR' => [ - 'teamsAccess.id' => ['team-id'], - 'assignedUsersAccess.id' => $this->user->id, - ], - ]); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyTeam2() - { - $filter = $this->createFilter(OnlyTeam::class); - - $this->initHelperMethods([ - ['hasTeamsField', false], - ]); - - $this->queryBuilder - ->expects($this->never()) - ->method('distinct'); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyTeam3() - { - $filter = $this->createFilter(OnlyTeam::class); - - $this->initHelperMethods([ - ['hasTeamsField', true], - ['hasAssignedUsersField', false], - ['hasAssignedUserField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('distinct'); - - $this->queryBuilder - ->expects($this->exactly(1)) - ->method('leftJoin') - ->withConsecutive( - ['teams', 'teamsAccess'], - ); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'OR' => [ - 'teamsAccess.id' => ['team-id'], - 'assignedUserId' => $this->user->id, - ], - ]) - ->willReturn($this->queryBuilder); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyTeam4() - { - $filter = $this->createFilter(OnlyTeam::class); - - $this->initHelperMethods([ - ['hasTeamsField', true], - ['hasAssignedUsersField', false], - ['hasAssignedUserField', false], - ['hasCreatedByField', true], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('distinct'); - - $this->queryBuilder - ->expects($this->exactly(1)) - ->method('leftJoin') - ->withConsecutive( - ['teams', 'teamsAccess'], - ) - ->willReturn($this->queryBuilder); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'OR' => [ - 'teamsAccess.id' => ['team-id'], - 'createdById' => $this->user->id, - ], - ]); - - $filter->apply($this->queryBuilder); - } - - public function testOnlyTeam5() - { - $filter = $this->createFilter(OnlyTeam::class); - - $this->initHelperMethods([ - ['hasTeamsField', true], - ['hasAssignedUsersField', false], - ['hasAssignedUserField', false], - ['hasCreatedByField', false], - ]); - - $this->queryBuilder - ->expects($this->once()) - ->method('distinct'); - - $this->queryBuilder - ->expects($this->exactly(1)) - ->method('leftJoin') - ->withConsecutive( - ['teams', 'teamsAccess'], - ) - ->willReturn($this->queryBuilder); - - $this->queryBuilder - ->expects($this->once()) - ->method('where') - ->with([ - 'OR' => [ - 'teamsAccess.id' => ['team-id'], - ], - ]); - - $filter->apply($this->queryBuilder); - } - public function testPortalOnlyAccount1() { $filter = $this->createFilter(PortalOnlyAccount::class); diff --git a/tests/unit/Espo/Core/Select/Where/ConverterTest.php b/tests/unit/Espo/Core/Select/Where/ConverterTest.php index 5e97ddc10e..83c3aa927a 100644 --- a/tests/unit/Espo/Core/Select/Where/ConverterTest.php +++ b/tests/unit/Espo/Core/Select/Where/ConverterTest.php @@ -106,7 +106,7 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $this->scanner, $this->itemConverterFactory, $this->randomStringGenerator, - $this->entityManager, + //$this->entityManager, $this->ormDefs, $this->config, $this->metadata @@ -447,19 +447,6 @@ class ConverterTest extends \PHPUnit\Framework\TestCase ], ]); - $sqQueryBuilder = $this->createMock(QueryBuilder::class); - - $this->baseQueryBuilder - ->expects($this->once()) - ->method('select') - ->willReturn($sqQueryBuilder); - - $sqQueryBuilder - ->expects($this->once()) - ->method('from') - ->with($this->entityType) - ->willReturn($sqQueryBuilder); - $sqItem = Item::fromRaw([ 'type' => 'and', 'value' => [ @@ -474,26 +461,10 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $this->scanner ->method('applyLeftJoins') ->withConsecutive( - [ - $sqQueryBuilder, $sqItem - ], - [ - $this->queryBuilder, $item - ] + [$this->isInstanceOf(QueryBuilder::class), $sqItem], + [$this->queryBuilder, $item] ); - $query = Select::fromRaw([ - 'select' => ['id'], - 'from' => $this->entityType, - 'leftJoins' => [['test']], - 'joins' => [], - ]); - - $sqQueryBuilder - ->expects($this->once()) - ->method('build') - ->willReturn($query); - $whereClause = $this->converter->convert($this->queryBuilder, $item); $expected = [ @@ -505,7 +476,7 @@ class ConverterTest extends \PHPUnit\Framework\TestCase ['test1=' => 'value1'], ], ], - 'leftJoins' => [['test']], + 'leftJoins' => [], 'joins' => [], ], ]; @@ -542,6 +513,7 @@ class ConverterTest extends \PHPUnit\Framework\TestCase 'type' => Entity::MANY_MANY, 'entity' => 'Foreign', 'midKeys' => ['localId', 'foreignId'], + 'relationName' => 'entityEntity' ], ], ], @@ -552,7 +524,7 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $alias = $link . 'LinkedWithFilterRandom'; $this->queryBuilder - ->expects($this->once()) + ->expects($this->never()) ->method('distinct'); $this->queryBuilder @@ -565,7 +537,16 @@ class ConverterTest extends \PHPUnit\Framework\TestCase ); $expected = [ - $alias . 'Middle.foreignId' => $value, + 'id=s' => QueryBuilder::create() + ->select('id') + ->from($this->entityType) + ->leftJoin('EntityEntity', $alias, [ + "{$alias}.localId:" => 'id', + "{$alias}.deleted" => 0, + ]) + ->where(["{$alias}.foreignId" => $value]) + ->build() + ->getRaw() ]; $whereClause = $this->converter->convert($this->queryBuilder, $item);