select optimizations

This commit is contained in:
Yuri Kuznetsov
2023-02-05 17:38:08 +02:00
parent 4cd0961f80
commit bc435b0729
25 changed files with 718 additions and 962 deletions
@@ -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()]);
}
}
@@ -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
)
);
}
}
@@ -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,
]);
}
}
@@ -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);
@@ -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
{
@@ -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
@@ -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
{
}
{}
}
@@ -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
{
@@ -72,7 +72,7 @@ class ForeignOnlyTeam implements Filter
return;
}
$teamIdList = $this->user->getLinkMultipleIdList('teams') ?? [];
$teamIdList = $this->user->getTeamIdList();
if (count($teamIdList) === 0) {
$queryBuilder->where([
@@ -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
{
}
{}
}
@@ -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]);
}
}
@@ -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()]);
}
}
}
@@ -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()]);
}
}
@@ -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
{
}
{}
}
@@ -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]);
}
}
@@ -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]);
}
}
@@ -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]);
}
}
@@ -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<mixed,mixed>
* @return array<int|string,mixed>
* @throws Error
*/
private function itemToList(Item $item): array
@@ -111,7 +101,7 @@ class Converter
}
/**
* @return ?array<mixed,mixed>
* @return ?array<int|string, mixed>
* @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<mixed,mixed>
* @return array<int|string, mixed>
* @throws Error
*/
private function applyInCategory(QueryBuilder $queryBuilder, string $attribute, $value): array
@@ -207,7 +202,7 @@ class Converter
/**
* @param mixed $value
* @return array<mixed,mixed>
* @return array<int|string ,mixed>
* @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,
File diff suppressed because it is too large Load Diff
@@ -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)) {
+3
View File
@@ -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 */
@@ -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()
)
);
}
}
@@ -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()
)
);
}
}
@@ -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);
@@ -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);