Files
espocrm/application/Espo/Core/Select/SelectBuilder.php
2021-03-28 13:43:16 +03:00

528 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\{
Factory\ApplierFactory,
Appliers\WhereApplier,
Appliers\SelectApplier,
Appliers\OrderApplier,
Appliers\LimitApplier,
Appliers\AccessControlFilterApplier,
Appliers\PrimaryFilterApplier,
Appliers\BoolFilterListApplier,
Appliers\TextFilterApplier,
Appliers\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);
}
}