mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-30 07:56:05 +00:00
564 lines
14 KiB
PHP
564 lines
14 KiB
PHP
<?php
|
|
/************************************************************************
|
|
* This file is part of EspoCRM.
|
|
*
|
|
* EspoCRM - Open Source CRM application.
|
|
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
|
* Website: https://www.espocrm.com
|
|
*
|
|
* EspoCRM is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* EspoCRM is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of this program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU General Public License version 3.
|
|
*
|
|
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
|
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
|
************************************************************************/
|
|
|
|
namespace Espo\Core\Select;
|
|
|
|
use Espo\Core\Select\{
|
|
Applier\Factory as ApplierFactory,
|
|
Applier\Appliers\Where as WhereApplier,
|
|
Applier\Appliers\Select as SelectApplier,
|
|
Applier\Appliers\Order as OrderApplier,
|
|
Applier\Appliers\Limit as LimitApplier,
|
|
Applier\Appliers\AccessControlFilter as AccessControlFilterApplier,
|
|
Applier\Appliers\PrimaryFilter as PrimaryFilterApplier,
|
|
Applier\Appliers\BoolFilterList as BoolFilterListApplier,
|
|
Applier\Appliers\TextFilter as TextFilterApplier,
|
|
Applier\Appliers\Additional as AdditionalApplier,
|
|
Where\Params as WhereParams,
|
|
Where\Item as WhereItem,
|
|
Order\Params as OrderParams,
|
|
Text\FilterParams as TextFilterParams,
|
|
};
|
|
|
|
use Espo\{
|
|
ORM\QueryParams\Select as Query,
|
|
ORM\QueryParams\SelectBuilder as QueryBuilder,
|
|
Entities\User,
|
|
};
|
|
|
|
use LogicException;
|
|
|
|
/**
|
|
* Builds select queries for ORM. Applies search parameters(passed from front-end),
|
|
* ACL restrictions, filters, etc.
|
|
*/
|
|
class SelectBuilder
|
|
{
|
|
private $entityType = null;
|
|
|
|
private $queryBuilder;
|
|
|
|
private $user = null;
|
|
|
|
private $sourceQuery = null;
|
|
|
|
private $searchParams = null;
|
|
|
|
private $applyAccessControlFilter = false;
|
|
|
|
private $applyDefaultOrder = false;
|
|
|
|
private $textFilter = null;
|
|
|
|
private $primaryFilter = null;
|
|
|
|
private $boolFilterList = [];
|
|
|
|
private $whereItemList = [];
|
|
|
|
private $applyWherePermissionCheck = false;
|
|
|
|
private $applyComplexExpressionsForbidden = false;
|
|
|
|
private $applierFactory;
|
|
|
|
public function __construct(User $user, ApplierFactory $applierFactory)
|
|
{
|
|
$this->applierFactory = $applierFactory;
|
|
|
|
$this->user = $user;
|
|
}
|
|
|
|
/**
|
|
* Specify an entity type to select from.
|
|
*/
|
|
public function from(string $entityType): self
|
|
{
|
|
if ($this->sourceQuery) {
|
|
throw new LogicException("Can't call 'from' after 'clone'.");
|
|
}
|
|
|
|
$this->entityType = $entityType;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Start building from an existing select query.
|
|
*/
|
|
public function clone(Query $query): self
|
|
{
|
|
if ($this->entityType && $this->entityType !== $query->getFrom()) {
|
|
throw new LogicException("Not matching entity type.");
|
|
}
|
|
|
|
$this->entityType = $query->getFrom();
|
|
|
|
$this->sourceQuery = $query;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Build a result query.
|
|
*/
|
|
public function build(): Query
|
|
{
|
|
return $this->buildQueryBuilder()->build();
|
|
}
|
|
|
|
/**
|
|
* Build an ORM query builder. Used to continue building but by means of ORM.
|
|
*/
|
|
public function buildQueryBuilder(): QueryBuilder
|
|
{
|
|
$this->queryBuilder = new OrmSelectBuilder();
|
|
|
|
if (!$this->entityType) {
|
|
throw new LogicException("No entity type.");
|
|
}
|
|
|
|
if ($this->sourceQuery) {
|
|
$this->queryBuilder->clone($this->sourceQuery);
|
|
}
|
|
else {
|
|
$this->queryBuilder->from($this->entityType);
|
|
}
|
|
|
|
$this->applyFromSearchParams();
|
|
|
|
if (count($this->whereItemList)) {
|
|
$this->applyWhereItemList();
|
|
}
|
|
|
|
if ($this->applyDefaultOrder) {
|
|
$this->applyDefaultOrder();
|
|
}
|
|
|
|
if ($this->primaryFilter) {
|
|
$this->applyPrimaryFilter();
|
|
}
|
|
|
|
if (count($this->boolFilterList)) {
|
|
$this->applyBoolFilterList();
|
|
}
|
|
|
|
if ($this->textFilter) {
|
|
$this->applyTextFilter();
|
|
}
|
|
|
|
if ($this->applyAccessControlFilter) {
|
|
$this->applyAccessControlFilter();
|
|
}
|
|
|
|
$this->applyAdditional();
|
|
|
|
return $this->queryBuilder;
|
|
}
|
|
|
|
/**
|
|
* Switch a user for whom a select query will be built.
|
|
*/
|
|
public function forUser(User $user): self
|
|
{
|
|
$this->user = $user;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply search parameters.
|
|
*
|
|
* Note: If there's no order set in the search parameters then a default order will be applied.
|
|
*/
|
|
public function withSearchParams(SearchParams $searchParams): self
|
|
{
|
|
$this->searchParams = $searchParams;
|
|
|
|
$this->withBoolFilterList(
|
|
$searchParams->getBoolFilterList()
|
|
);
|
|
|
|
$primaryFilter = $searchParams->getPrimaryFilter();
|
|
|
|
if ($primaryFilter) {
|
|
$this->withPrimaryFilter($primaryFilter);
|
|
}
|
|
|
|
$textFilter = $searchParams->getTextFilter();
|
|
|
|
if ($textFilter) {
|
|
$this->withTextFilter($textFilter);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply maximum restrictions for a user.
|
|
*/
|
|
public function withStrictAccessControl(): self
|
|
{
|
|
$this->withAccessControlFilter();
|
|
$this->withWherePermissionCheck();
|
|
$this->withComplexExpressionsForbidden();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply an access control filter.
|
|
*/
|
|
public function withAccessControlFilter(): self
|
|
{
|
|
$this->applyAccessControlFilter = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a default order.
|
|
*/
|
|
public function withDefaultOrder(): self
|
|
{
|
|
$this->applyDefaultOrder = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Check permissions to where items.
|
|
*/
|
|
public function withWherePermissionCheck(): self
|
|
{
|
|
$this->applyWherePermissionCheck = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Forbid complex expression usage.
|
|
*/
|
|
public function withComplexExpressionsForbidden(): self
|
|
{
|
|
$this->applyComplexExpressionsForbidden = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a text filter.
|
|
*/
|
|
public function withTextFilter(string $textFilter): self
|
|
{
|
|
$this->textFilter = $textFilter;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a primary filter.
|
|
*/
|
|
public function withPrimaryFilter(string $primaryFilter): self
|
|
{
|
|
$this->primaryFilter = $primaryFilter;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a bool filter.
|
|
*/
|
|
public function withBoolFilter(string $boolFilter): self
|
|
{
|
|
$this->boolFilterList[] = $boolFilter;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a list of bool filters.
|
|
*/
|
|
public function withBoolFilterList(array $boolFilterList): self
|
|
{
|
|
$this->boolFilterList = array_merge($this->boolFilterList, $boolFilterList);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Apply a Where Item.
|
|
*/
|
|
public function withWhere(WhereItem $whereItem): self
|
|
{
|
|
$this->whereItemList[] = $whereItem;
|
|
|
|
return $this;
|
|
}
|
|
|
|
private function applyPrimaryFilter(): void
|
|
{
|
|
$this->createPrimaryFilterApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$this->primaryFilter
|
|
);
|
|
}
|
|
|
|
private function applyBoolFilterList(): void
|
|
{
|
|
$this->createBoolFilterListApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$this->boolFilterList
|
|
);
|
|
}
|
|
|
|
private function applyTextFilter(): void
|
|
{
|
|
$noFullTextSearch = false;
|
|
|
|
if ($this->searchParams && $this->searchParams->noFullTextSearch()) {
|
|
$noFullTextSearch = true;
|
|
}
|
|
|
|
$this->createTextFilterApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$this->textFilter,
|
|
TextFilterParams::fromArray([
|
|
'noFullTextSearch' => $noFullTextSearch,
|
|
])
|
|
);
|
|
}
|
|
|
|
private function applyAccessControlFilter(): void
|
|
{
|
|
$this->createAccessControlFilterApplier()
|
|
->apply(
|
|
$this->queryBuilder
|
|
);
|
|
}
|
|
|
|
private function applyDefaultOrder(): void
|
|
{
|
|
$order = null;
|
|
|
|
if ($this->searchParams) {
|
|
$order = $this->searchParams->getOrder();
|
|
}
|
|
|
|
$params = OrderParams::fromArray([
|
|
'forceDefault' => true,
|
|
'order' => $order,
|
|
]);
|
|
|
|
$this->createOrderApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$params
|
|
);
|
|
}
|
|
|
|
private function applyWhereItemList(): void
|
|
{
|
|
foreach ($this->whereItemList as $whereItem) {
|
|
$this->applyWhereItem($whereItem);
|
|
}
|
|
}
|
|
|
|
private function applyWhereItem(WhereItem $whereItem): void
|
|
{
|
|
$params = WhereParams::fromArray([
|
|
'applyPermissionCheck' => $this->applyWherePermissionCheck,
|
|
'forbidComplexExpressions' => $this->applyComplexExpressionsForbidden,
|
|
]);
|
|
|
|
$this->createWhereApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$whereItem,
|
|
$params
|
|
);
|
|
}
|
|
|
|
private function applyFromSearchParams(): void
|
|
{
|
|
if (!$this->searchParams) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
!$this->applyDefaultOrder &&
|
|
($this->searchParams->getOrderBy() || $this->searchParams->getOrder())
|
|
) {
|
|
$params = OrderParams::fromArray([
|
|
'forbidComplexExpressions' => $this->applyComplexExpressionsForbidden,
|
|
'orderBy' => $this->searchParams->getOrderBy(),
|
|
'order' => $this->searchParams->getOrder(),
|
|
]);
|
|
|
|
$this->createOrderApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$params
|
|
);
|
|
}
|
|
|
|
if (!$this->searchParams->getOrderBy() && !$this->searchParams->getOrder()) {
|
|
$this->withDefaultOrder();
|
|
}
|
|
|
|
if ($this->searchParams->getMaxSize() || $this->searchParams->getOffset()) {
|
|
$this->createLimitApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$this->searchParams->getOffset(),
|
|
$this->searchParams->getMaxSize()
|
|
);
|
|
}
|
|
|
|
if ($this->searchParams->getSelect()) {
|
|
$this->createSelectApplier()
|
|
->apply(
|
|
$this->queryBuilder,
|
|
$this->searchParams
|
|
);
|
|
}
|
|
|
|
if ($this->searchParams->getWhere()) {
|
|
$whereItem = WhereItem::fromRaw([
|
|
'type' => 'and',
|
|
'value' => $this->searchParams->getWhere(),
|
|
]);
|
|
|
|
$this->whereItemList[] = $whereItem;
|
|
}
|
|
}
|
|
|
|
private function applyAdditional(): void
|
|
{
|
|
$searchParams = SearchParams::fromRaw([
|
|
'boolFilterList' => $this->boolFilterList,
|
|
'primaryFilter' => $this->primaryFilter,
|
|
'textFilter' => $this->textFilter,
|
|
]);
|
|
|
|
if ($this->searchParams) {
|
|
$searchParams = SearchParams::merge($searchParams, $this->searchParams);
|
|
}
|
|
|
|
$this->createAdditionalApplier()->apply(
|
|
$this->queryBuilder,
|
|
$searchParams
|
|
);
|
|
}
|
|
|
|
private function createWhereApplier(): WhereApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::WHERE
|
|
);
|
|
}
|
|
|
|
private function createSelectApplier(): SelectApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::SELECT
|
|
);
|
|
}
|
|
|
|
private function createOrderApplier(): OrderApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::ORDER
|
|
);
|
|
}
|
|
|
|
private function createLimitApplier(): LimitApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::LIMIT
|
|
);
|
|
}
|
|
|
|
private function createAccessControlFilterApplier(): AccessControlFilterApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::ACCESS_CONTROL_FILTER
|
|
);
|
|
}
|
|
|
|
private function createTextFilterApplier(): TextFilterApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::TEXT_FILTER
|
|
);
|
|
}
|
|
|
|
private function createPrimaryFilterApplier(): PrimaryFilterApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::PRIMARY_FILTER
|
|
);
|
|
}
|
|
|
|
private function createBoolFilterListApplier(): BoolFilterListApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::BOOL_FILTER_LIST
|
|
);
|
|
}
|
|
|
|
private function createAdditionalApplier(): AdditionalApplier
|
|
{
|
|
return $this->applierFactory->create(
|
|
$this->entityType,
|
|
$this->user,
|
|
ApplierFactory::ADDITIONAL
|
|
);
|
|
}
|
|
}
|