2999 lines
99 KiB
PHP
2999 lines
99 KiB
PHP
<?php
|
||
/************************************************************************
|
||
* This file is part of EspoCRM.
|
||
*
|
||
* EspoCRM – Open Source CRM application.
|
||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||
* Website: https://www.espocrm.com
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU Affero General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program 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 Affero General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Affero General Public License
|
||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||
*
|
||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||
************************************************************************/
|
||
|
||
namespace Espo\Core\Select;
|
||
|
||
use Espo\Core\Exceptions\BadRequest;
|
||
use Espo\Core\Exceptions\Forbidden;
|
||
|
||
use Espo\Core\Acl;
|
||
use Espo\Core\AclManager;
|
||
use Espo\Core\InjectableFactory;
|
||
use Espo\Core\ORM\EntityManager;
|
||
use Espo\Core\ORM\Type\FieldType;
|
||
use Espo\Core\Utils\Config;
|
||
use Espo\Core\Utils\FieldUtil;
|
||
use Espo\Core\Utils\Metadata;
|
||
|
||
use Espo\Entities\StreamSubscription;
|
||
use Espo\ORM\Defs\Params\RelationParam;
|
||
use Espo\ORM\Entity;
|
||
use Espo\ORM\Name\Attribute;
|
||
use Espo\ORM\Query\Select as SelectQuery;
|
||
use Espo\ORM\QueryComposer\BaseQueryComposer as QueryComposer;
|
||
|
||
use Espo\Entities\User;
|
||
|
||
use DateTime;
|
||
use DateTimeZone;
|
||
use DateInterval;
|
||
|
||
use Espo\ORM\Type\AttributeType;
|
||
use ReflectionMethod;
|
||
|
||
/**
|
||
* @deprecated As of v7.0. Use Select framework and SelectBuilder instead.
|
||
* @todo Remove in v10.0.
|
||
*/
|
||
class SelectManager
|
||
{
|
||
protected $entityType;
|
||
|
||
private $seed = null;
|
||
|
||
private $userTimeZone = null;
|
||
|
||
protected $additionalFilterTypeList = ['inCategory', 'isUserFromTeams'];
|
||
|
||
protected $aclAttributeList = ['assignedUserId', 'createdById'];
|
||
|
||
protected $aclPortalAttributeList = ['assignedUserId', 'createdById', 'contactId', 'accountId'];
|
||
|
||
protected $textFilterUseContainsAttributeList = [];
|
||
|
||
protected $selectAttributesDependancyMap = [];
|
||
|
||
protected $fullTextOrderType = self::FT_ORDER_COMBINTED;
|
||
|
||
protected $fullTextRelevanceThreshold = null;
|
||
|
||
const FT_ORDER_COMBINTED = 0;
|
||
|
||
const FT_ORDER_RELEVANCE = 1;
|
||
|
||
const FT_ORDER_ORIGINAL = 3;
|
||
|
||
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
|
||
|
||
const MIN_LENGTH_FOR_FULL_TEXT_SEARCH = 4;
|
||
|
||
protected $fullTextOrderRelevanceDivider = 5;
|
||
|
||
protected $fullTextSearchDataCacheHash = [];
|
||
|
||
protected $entityManager;
|
||
|
||
protected $user;
|
||
|
||
protected $acl;
|
||
|
||
protected $aclManager;
|
||
|
||
protected $metadata;
|
||
|
||
protected $config;
|
||
|
||
protected $fieldUtil;
|
||
|
||
protected $injectableFactory;
|
||
|
||
private $selectBuilderFactory;
|
||
|
||
public function __construct(
|
||
EntityManager $entityManager,
|
||
User $user,
|
||
Acl $acl,
|
||
AclManager $aclManager,
|
||
Metadata $metadata,
|
||
Config $config,
|
||
FieldUtil $fieldUtil,
|
||
InjectableFactory $injectableFactory,
|
||
SelectBuilderFactory $selectBuilderFactory
|
||
) {
|
||
$this->entityManager = $entityManager;
|
||
$this->user = $user;
|
||
$this->acl = $acl;
|
||
$this->aclManager = $aclManager;
|
||
$this->metadata = $metadata;
|
||
$this->config = $config;
|
||
$this->fieldUtil = $fieldUtil;
|
||
$this->injectableFactory = $injectableFactory;
|
||
$this->selectBuilderFactory = $selectBuilderFactory;
|
||
}
|
||
|
||
protected function getEntityManager() : EntityManager
|
||
{
|
||
return $this->entityManager;
|
||
}
|
||
|
||
protected function getMetadata() : Metadata
|
||
{
|
||
return $this->metadata;
|
||
}
|
||
|
||
protected function getUser() : User
|
||
{
|
||
return $this->user;
|
||
}
|
||
|
||
protected function getAcl() : Acl
|
||
{
|
||
return $this->acl;
|
||
}
|
||
|
||
protected function getConfig() : Config
|
||
{
|
||
return $this->config;
|
||
}
|
||
|
||
protected function getAclManager() : AclManager
|
||
{
|
||
return $this->aclManager;
|
||
}
|
||
|
||
protected function getInjectableFactory() : InjectableFactory
|
||
{
|
||
return $this->injectableFactory;
|
||
}
|
||
|
||
protected function getFieldUtil() : FieldUtil
|
||
{
|
||
return $this->fieldUtil;
|
||
}
|
||
|
||
public function setEntityType(string $entityType)
|
||
{
|
||
$this->entityType = $entityType;
|
||
}
|
||
|
||
protected function getEntityType() : string
|
||
{
|
||
return $this->entityType;
|
||
}
|
||
|
||
protected function limit(?int $offset, ?int $maxSize, array &$result)
|
||
{
|
||
if (!is_null($offset)) {
|
||
$result['offset'] = $offset;
|
||
}
|
||
if (!is_null($maxSize)) {
|
||
$result['limit'] = $maxSize;
|
||
}
|
||
}
|
||
|
||
protected function order(string $sortBy, $desc, array &$result)
|
||
{
|
||
if (is_string($desc)) {
|
||
$desc = $desc === strtolower('desc');
|
||
}
|
||
|
||
if ($sortBy) {
|
||
$result['orderBy'] = $sortBy;
|
||
$type = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'type']);
|
||
|
||
if (in_array($type, [FieldType::LINK, FieldType::FILE, FieldType::IMAGE, FieldType::LINK_ONE])) {
|
||
$result['orderBy'] .= 'Name';
|
||
} else if ($type === FieldType::LINK_PARENT) {
|
||
$result['orderBy'] .= 'Type';
|
||
} else if ($type === FieldType::ADDRESS) {
|
||
$result['orderBy'] = [
|
||
[$sortBy . 'Country', $desc],
|
||
[$sortBy . 'City', $desc],
|
||
[$sortBy . 'Street', $desc],
|
||
];
|
||
} else if ($type === FieldType::ENUM) {
|
||
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
|
||
|
||
if ($list && is_array($list) && count($list)) {
|
||
if ($this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'isSorted'])) {
|
||
asort($list);
|
||
}
|
||
|
||
if ($desc) {
|
||
$list = array_reverse($list);
|
||
}
|
||
|
||
foreach ($list as $i => $listItem) {
|
||
$list[$i] = str_replace(',', '_COMMA_', $listItem);
|
||
}
|
||
|
||
$result['orderBy'] = [['LIST:' . $sortBy . ':' . implode(',', $list)]];
|
||
}
|
||
} else {
|
||
if (strpos($sortBy, '.') === false && strpos($sortBy, ':') === false) {
|
||
if (!$this->getSeed()->hasAttribute($sortBy)) {
|
||
throw new BadRequest("Order by non-existing field '{$sortBy}'.");
|
||
}
|
||
}
|
||
}
|
||
|
||
$orderByAttribute = null;
|
||
|
||
if (!is_array($result['orderBy'])) {
|
||
$orderByAttribute = $result['orderBy'];
|
||
$result['orderBy'] = [[$result['orderBy'], $desc]];
|
||
}
|
||
|
||
if (
|
||
$sortBy != Attribute::ID &&
|
||
(!$orderByAttribute || !$this->getSeed()->getAttributeParam($orderByAttribute, 'unique')) &&
|
||
$this->getSeed()->hasAttribute(Attribute::ID)
|
||
) {
|
||
$result['orderBy'][] = [Attribute::ID, $desc];
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (!$desc) {
|
||
$result['order'] = 'ASC';
|
||
} else {
|
||
$result['order'] = 'DESC';
|
||
}
|
||
}
|
||
|
||
protected function getTextFilterFieldList() : array
|
||
{
|
||
return $this->getMetadata()
|
||
->get(['entityDefs', $this->entityType, 'collection', 'textFilterFields'], ['name']);
|
||
}
|
||
|
||
protected function getSeed(): Entity
|
||
{
|
||
if (empty($this->seed)) {
|
||
$this->seed = $this->entityManager->getNewEntity($this->entityType);
|
||
}
|
||
return $this->seed;
|
||
}
|
||
|
||
public function applyWhere(array $where, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->where($where, $result);
|
||
}
|
||
|
||
protected function where(array $where, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
|
||
$boolFilterList = [];
|
||
|
||
foreach ($where as $item) {
|
||
if (!isset($item['type'])) continue;
|
||
|
||
if ($item['type'] == 'bool' && !empty($item['value']) && is_array($item['value'])) {
|
||
$boolOr = [];
|
||
foreach ($item['value'] as $filter) {
|
||
$boolFilterList[] = $filter;
|
||
}
|
||
} else if ($item['type'] == 'textFilter') {
|
||
if (isset($item['value']) || $item['value'] !== '') {
|
||
$this->textFilter($item['value'], $result);
|
||
}
|
||
} else if ($item['type'] == 'primary' && !empty($item['value'])) {
|
||
$this->applyPrimaryFilter($item['value'], $result);
|
||
}
|
||
}
|
||
|
||
if (count($boolFilterList)) {
|
||
$this->applyBoolFilterList($boolFilterList, $result);
|
||
}
|
||
|
||
$whereClause = $this->convertWhere($where, false, $result);
|
||
|
||
$result['whereClause'] = array_merge($result['whereClause'], $whereClause);
|
||
|
||
$this->applyLeftJoinsFromWhere($where, $result);
|
||
}
|
||
|
||
/**
|
||
* Convert 'where' parameters from the frontend format to the format needed by ORM.
|
||
*
|
||
* @return array Where clause for ORM.
|
||
*/
|
||
public function convertWhere(array $where, bool $ignoreAdditionaFilterTypes = false, array &$result = []) : array
|
||
{
|
||
$whereClause = [];
|
||
|
||
$ignoreTypeList = array_merge(['bool', 'primary'], $this->additionalFilterTypeList);
|
||
|
||
foreach ($where as $item) {
|
||
if (!isset($item['type'])) continue;
|
||
|
||
$type = $item['type'];
|
||
if (!in_array($type, $ignoreTypeList)) {
|
||
$part = $this->getWherePart($item, $result);
|
||
if (!empty($part)) {
|
||
$whereClause[] = $part;
|
||
}
|
||
} else {
|
||
if (!$ignoreAdditionaFilterTypes && in_array($type, $this->additionalFilterTypeList)) {
|
||
if (!empty($item['value'])) {
|
||
$methodName = 'apply' . ucfirst($type);
|
||
|
||
if (method_exists($this, $methodName)) {
|
||
$attribute = null;
|
||
if (isset($item['field'])) {
|
||
$attribute = $item['field'];
|
||
}
|
||
if (isset($item['attribute'])) {
|
||
$attribute = $item['attribute'];
|
||
}
|
||
if ($attribute) {
|
||
$this->$methodName($attribute, $item['value'], $result);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $whereClause;
|
||
}
|
||
|
||
protected function applyLinkedWith(string $link, $idsValue, array &$result)
|
||
{
|
||
$part = [];
|
||
|
||
if (is_array($idsValue) && count($idsValue) == 1) {
|
||
$idsValue = $idsValue[0];
|
||
}
|
||
|
||
$seed = $this->getSeed();
|
||
|
||
if (!$seed->hasRelation($link)) {
|
||
return;
|
||
}
|
||
|
||
$relDefs = $this->entityManager->getMetadata()->get($this->entityType, ['relations']);
|
||
|
||
$relationType = $seed->getRelationType($link);
|
||
|
||
if ($relationType == 'manyMany') {
|
||
$this->addLeftJoin([$link, $link . 'Filter'], $result);
|
||
$midKeys = $seed->getRelationParam($link, 'midKeys');
|
||
|
||
if (!empty($midKeys)) {
|
||
$key = $midKeys[1];
|
||
$part[$link . 'Filter' . 'Middle.' . $key] = $idsValue;
|
||
}
|
||
|
||
} else if ($relationType == 'hasMany') {
|
||
$alias = $link . 'Filter';
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
|
||
$part[$alias . '.id'] = $idsValue;
|
||
|
||
} else if ($relationType == 'belongsTo') {
|
||
$key = $seed->getRelationParam($link, 'key');
|
||
if (!empty($key)) {
|
||
$part[$key] = $idsValue;
|
||
}
|
||
|
||
} else if ($relationType == 'hasOne') {
|
||
$this->addJoin([$link, $link . 'Filter'], $result);
|
||
$part[$link . 'Filter' . '.id'] = $idsValue;
|
||
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
if (!empty($part)) {
|
||
$result['whereClause'][] = $part;
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
}
|
||
|
||
protected function applyIsUserFromTeams(string $link, $idsValue, array &$result)
|
||
{
|
||
if (is_array($idsValue) && count($idsValue) == 1) {
|
||
$idsValue = $idsValue[0];
|
||
}
|
||
|
||
$seed = $this->getSeed();
|
||
|
||
if (!$seed->hasRelation($link)) {
|
||
return;
|
||
}
|
||
|
||
$relationType = $seed->getRelationType($link);
|
||
|
||
if ($relationType == 'belongsTo') {
|
||
$key = $seed->getRelationParam($link, 'key');
|
||
|
||
$aliasName = 'usersTeams' . ucfirst($link) . strval(rand(10000, 99999));
|
||
|
||
$this->addLeftJoin([
|
||
'TeamUser',
|
||
$aliasName . 'Middle',
|
||
[
|
||
$aliasName . 'Middle.userId:' => $key,
|
||
$aliasName . 'Middle.deleted' => false,
|
||
]
|
||
], $result);
|
||
|
||
$result['whereClause'][] = [
|
||
$aliasName . 'Middle.teamId' => $idsValue
|
||
];
|
||
} else {
|
||
throw new BadRequest("Can't apply isUserFromTeams for link {$link}.");
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
}
|
||
|
||
public function applyInCategory(string $link, $value, array &$result)
|
||
{
|
||
$relDefs = $this->entityManager->getMetadata()->get($this->entityType, ['relations']);
|
||
|
||
if (empty($relDefs[$link])) {
|
||
throw new BadRequest("Can't apply inCategory for link {$link}.");
|
||
}
|
||
|
||
$defs = $relDefs[$link];
|
||
|
||
$foreignEntity = $defs['entity'] ?? null;
|
||
|
||
if (empty($foreignEntity)) {
|
||
throw new BadRequest("Can't apply inCategory for link {$link}.");
|
||
}
|
||
|
||
$pathName = lcfirst($foreignEntity) . 'Path';
|
||
|
||
if ($defs['type'] == 'manyMany') {
|
||
if (empty($defs['midKeys'])) {
|
||
throw new BadRequest("Can't apply inCategory for link {$link}.");
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
$this->addJoin($link, $result);
|
||
|
||
$key = $defs['midKeys'][1];
|
||
$middleName = $link . 'Middle';
|
||
|
||
$this->addJoin([
|
||
ucfirst($pathName),
|
||
$pathName,
|
||
[
|
||
"{$pathName}.descendorId:" => "{$middleName}.{$key}",
|
||
]
|
||
], $result);
|
||
|
||
$result['whereClause'][$pathName . '.ascendorId'] = $value;
|
||
|
||
return;
|
||
}
|
||
|
||
if ($defs['type'] == 'belongsTo') {
|
||
if (empty($defs['key'])) {
|
||
throw new BadRequest("Can't apply inCategory for link {$link}.");
|
||
}
|
||
|
||
$key = $defs['key'];
|
||
|
||
$this->addJoin([
|
||
ucfirst($pathName),
|
||
$pathName,
|
||
[
|
||
"{$pathName}.descendorId:" => "{$key}",
|
||
]
|
||
], $result);
|
||
|
||
$result['whereClause'][$pathName . '.ascendorId'] = $value;
|
||
|
||
return;
|
||
}
|
||
|
||
throw new BadRequest("Can't apply inCategory for link {$link}.");
|
||
}
|
||
|
||
protected function q(array $params, array &$result)
|
||
{
|
||
if (isset($params['q']) && $params['q'] !== '') {
|
||
$textFilter = $params['q'];
|
||
$this->textFilter($textFilter, $result, true);
|
||
}
|
||
}
|
||
|
||
public function manageAccess(array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->applyAccess($result);
|
||
}
|
||
|
||
public function manageTextFilter(string $textFilter, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->q(['q' => $textFilter], $result);
|
||
}
|
||
|
||
/**
|
||
* Get empty select parameters.
|
||
*/
|
||
public function getEmptySelectParams() : array
|
||
{
|
||
$result = [];
|
||
$this->prepareResult($result);
|
||
|
||
return $result;
|
||
}
|
||
|
||
protected function prepareResult(array &$result)
|
||
{
|
||
if (empty($result)) {
|
||
$result = [];
|
||
}
|
||
if (empty($result['from'])) {
|
||
$result['from'] = $this->entityType;
|
||
}
|
||
if (empty($result['joins'])) {
|
||
$result['joins'] = [];
|
||
}
|
||
if (empty($result['leftJoins'])) {
|
||
$result['leftJoins'] = [];
|
||
}
|
||
if (empty($result['whereClause'])) {
|
||
$result['whereClause'] = [];
|
||
}
|
||
if (empty($result['customJoin'])) {
|
||
$result['customJoin'] = '';
|
||
}
|
||
if (empty($result['additionalSelectColumns'])) {
|
||
$result['additionalSelectColumns'] = [];
|
||
}
|
||
if (empty($result['joinConditions'])) {
|
||
$result['joinConditions'] = [];
|
||
}
|
||
}
|
||
|
||
protected function checkIsPortal() : bool
|
||
{
|
||
return !!$this->getUser()->get('portalId');
|
||
}
|
||
|
||
protected function access(&$result)
|
||
{
|
||
if (!$this->checkIsPortal()) {
|
||
if ($this->getAcl()->checkReadOnlyOwn($this->getEntityType())) {
|
||
$this->accessOnlyOwn($result);
|
||
} else {
|
||
if (!$this->getUser()->isAdmin()) {
|
||
if ($this->getAcl()->checkReadOnlyTeam($this->getEntityType())) {
|
||
$this->accessOnlyTeam($result);
|
||
} else if ($this->getAcl()->checkReadNo($this->getEntityType())) {
|
||
$this->accessNo($result);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if ($this->getAcl()->checkReadOnlyOwn($this->getEntityType())) {
|
||
$this->accessPortalOnlyOwn($result);
|
||
} else {
|
||
if ($this->getAcl()->checkReadOnlyAccount($this->getEntityType())) {
|
||
$this->accessPortalOnlyAccount($result);
|
||
} else {
|
||
if ($this->getAcl()->checkReadOnlyContact($this->getEntityType())) {
|
||
$this->accessPortalOnlyContact($result);
|
||
} else if ($this->getAcl()->checkReadNo($this->getEntityType())) {
|
||
$this->accessNo($result);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
protected function accessNo(array &$result)
|
||
{
|
||
$result['whereClause'][] = [
|
||
'id' => null
|
||
];
|
||
}
|
||
|
||
protected function accessOnlyOwn(&$result)
|
||
{
|
||
if ($this->hasAssignedUsersField()) {
|
||
$this->setDistinct(true, $result);
|
||
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
|
||
$result['whereClause'][] = [
|
||
'assignedUsersAccess.id' => $this->getUser()->getId()
|
||
];
|
||
return;
|
||
}
|
||
|
||
if ($this->hasAssignedUserField()) {
|
||
$result['whereClause'][] = [
|
||
'assignedUserId' => $this->getUser()->getId()
|
||
];
|
||
return;
|
||
}
|
||
|
||
if ($this->hasCreatedByField()) {
|
||
$result['whereClause'][] = [
|
||
'createdById' => $this->getUser()->getId()
|
||
];
|
||
}
|
||
}
|
||
|
||
protected function accessOnlyTeam(&$result)
|
||
{
|
||
if (!$this->hasTeamsField()) {
|
||
return;
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
$this->addLeftJoin(['teams', 'teamsAccess'], $result);
|
||
|
||
if ($this->hasAssignedUsersField()) {
|
||
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
|
||
$result['whereClause'][] = [
|
||
'OR' => [
|
||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams'),
|
||
'assignedUsersAccess.id' => $this->getUser()->getId()
|
||
]
|
||
];
|
||
return;
|
||
}
|
||
|
||
$or = [
|
||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams')
|
||
];
|
||
if ($this->hasAssignedUserField()) {
|
||
$or['assignedUserId'] = $this->getUser()->getId();
|
||
} else if ($this->hasCreatedByField()) {
|
||
$or['createdById'] = $this->getUser()->getId();
|
||
}
|
||
$result['whereClause'][] = [
|
||
'OR' => $or
|
||
];
|
||
}
|
||
|
||
protected function accessPortalOnlyOwn(&$result)
|
||
{
|
||
if ($this->getSeed()->hasAttribute('createdById')) {
|
||
$result['whereClause'][] = [
|
||
'createdById' => $this->getUser()->getId()
|
||
];
|
||
} else {
|
||
$result['whereClause'][] = [
|
||
'id' => null
|
||
];
|
||
}
|
||
}
|
||
|
||
protected function accessPortalOnlyContact(&$result)
|
||
{
|
||
$or = [];
|
||
|
||
$contactId = $this->getUser()->get('contactId');
|
||
|
||
if ($contactId) {
|
||
if (
|
||
$this->getSeed()->hasAttribute('contactId') &&
|
||
$this->getSeed()->getRelationParam('contact', RelationParam::ENTITY) === 'Contact'
|
||
) {
|
||
$or['contactId'] = $contactId;
|
||
}
|
||
|
||
if (
|
||
$this->getSeed()->hasRelation('contacts') &&
|
||
$this->getSeed()->getRelationParam('contacts', RelationParam::ENTITY) === 'Contact'
|
||
) {
|
||
$this->addLeftJoin(['contacts', 'contactsAccess'], $result);
|
||
$this->setDistinct(true, $result);
|
||
$or['contactsAccess.id'] = $contactId;
|
||
}
|
||
}
|
||
|
||
if ($this->getSeed()->hasAttribute('createdById')) {
|
||
$or['createdById'] = $this->getUser()->getId();
|
||
}
|
||
|
||
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
|
||
$contactId = $this->getUser()->get('contactId');
|
||
if ($contactId) {
|
||
$or[] = [
|
||
'parentType' => 'Contact',
|
||
'parentId' => $contactId
|
||
];
|
||
}
|
||
}
|
||
|
||
if (!empty($or)) {
|
||
$result['whereClause'][] = [
|
||
'OR' => $or
|
||
];
|
||
} else {
|
||
$result['whereClause'][] = [
|
||
'id' => null
|
||
];
|
||
}
|
||
}
|
||
|
||
protected function accessPortalOnlyAccount(&$result)
|
||
{
|
||
$or = [];
|
||
|
||
$accountIdList = $this->getUser()->getLinkMultipleIdList('accounts');
|
||
$contactId = $this->getUser()->get('contactId');
|
||
|
||
if (count($accountIdList)) {
|
||
if (
|
||
$this->getSeed()->hasAttribute('accountId') &&
|
||
$this->getSeed()->getRelationParam('account', RelationParam::ENTITY) === 'Account'
|
||
) {
|
||
$or['accountId'] = $accountIdList;
|
||
}
|
||
if (
|
||
$this->getSeed()->hasRelation('accounts') &&
|
||
$this->getSeed()->getRelationParam('accounts', RelationParam::ENTITY) === 'Account'
|
||
) {
|
||
$this->addLeftJoin(['accounts', 'accountsAccess'], $result);
|
||
$this->setDistinct(true, $result);
|
||
$or['accountsAccess.id'] = $accountIdList;
|
||
}
|
||
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
|
||
$or[] = [
|
||
'parentType' => 'Account',
|
||
'parentId' => $accountIdList
|
||
];
|
||
if ($contactId) {
|
||
$or[] = [
|
||
'parentType' => 'Contact',
|
||
'parentId' => $contactId
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($contactId) {
|
||
if (
|
||
$this->getSeed()->hasAttribute('contactId') &&
|
||
$this->getSeed()->getRelationParam('contact', RelationParam::ENTITY) === 'Contact'
|
||
) {
|
||
$or['contactId'] = $contactId;
|
||
}
|
||
if (
|
||
$this->getSeed()->hasRelation('contacts') &&
|
||
$this->getSeed()->getRelationParam('contacts', RelationParam::ENTITY) === 'Contact'
|
||
) {
|
||
$this->addLeftJoin(['contacts', 'contactsAccess'], $result);
|
||
$this->setDistinct(true, $result);
|
||
$or['contactsAccess.id'] = $contactId;
|
||
}
|
||
}
|
||
|
||
if ($this->getSeed()->hasAttribute('createdById')) {
|
||
$or['createdById'] = $this->getUser()->getId();
|
||
}
|
||
|
||
if (!empty($or)) {
|
||
$result['whereClause'][] = [
|
||
'OR' => $or
|
||
];
|
||
} else {
|
||
$result['whereClause'][] = [
|
||
'id' => null
|
||
];
|
||
}
|
||
}
|
||
|
||
protected function hasAssignedUsersField() : bool
|
||
{
|
||
if ($this->getSeed()->hasRelation('assignedUsers') && $this->getSeed()->hasAttribute('assignedUsersIds')) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
protected function hasAssignedUserField() : bool
|
||
{
|
||
if ($this->getSeed()->hasAttribute('assignedUserId')) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
protected function hasCreatedByField() : bool
|
||
{
|
||
if ($this->getSeed()->hasAttribute('createdById')) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
protected function hasTeamsField() : bool
|
||
{
|
||
if ($this->getSeed()->hasRelation('teams') && $this->getSeed()->hasAttribute('teamsIds')) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public function getAclParams() : array
|
||
{
|
||
$result = [];
|
||
$this->applyAccess($result);
|
||
return $result;
|
||
}
|
||
|
||
public function buildSelectParams(
|
||
array $params,
|
||
bool $withAcl = false,
|
||
bool $checkWherePermission = false,
|
||
bool $forbidComplexExpressions = false
|
||
) : array {
|
||
|
||
return $this->getSelectParams($params, $withAcl, $checkWherePermission, $forbidComplexExpressions);
|
||
}
|
||
|
||
/**
|
||
* The same as buildSelectParams.
|
||
*/
|
||
public function getSelectParams(
|
||
array $params,
|
||
bool $withAcl = false,
|
||
bool $checkWherePermission = false,
|
||
bool $forbidComplexExpressions = false
|
||
) : array {
|
||
|
||
$builder = $this->selectBuilderFactory
|
||
->create()
|
||
->forUser($this->user)
|
||
->from($this->entityType);
|
||
|
||
if ($withAcl) {
|
||
$builder->withAccessControlFilter();
|
||
}
|
||
|
||
if ($checkWherePermission) {
|
||
$builder->withWherePermissionCheck();
|
||
}
|
||
|
||
if ($forbidComplexExpressions) {
|
||
$builder->withComplexExpressionsForbidden();
|
||
}
|
||
|
||
$builder->withSearchParams(SearchParams::fromRaw($params));
|
||
|
||
$raw = $builder->build()->getRaw();
|
||
|
||
$this->prepareResult($raw);
|
||
|
||
return $raw;
|
||
}
|
||
|
||
/**
|
||
* Apply default order to select parameters.
|
||
*/
|
||
public function applyDefaultOrder(array &$result)
|
||
{
|
||
$orderBy = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
|
||
$order = $result['order'] ?? null;
|
||
|
||
if (!$order && !is_array($orderBy)) {
|
||
$order = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'order']) ?? null;
|
||
}
|
||
|
||
if ($orderBy) {
|
||
$this->applyOrder($orderBy, $order, $result);
|
||
} else {
|
||
$result['order'] = $order;
|
||
}
|
||
}
|
||
|
||
public function checkWhere(array $where, bool $checkWherePermission = true, bool $forbidComplexExpressions = false)
|
||
{
|
||
foreach ($where as $w) {
|
||
$attribute = null;
|
||
if (isset($w['field'])) {
|
||
$attribute = $w['field'];
|
||
}
|
||
if (isset($w['attribute'])) {
|
||
$attribute = $w['attribute'];
|
||
}
|
||
|
||
$type = null;
|
||
if (isset($w['type'])) {
|
||
$type = $w['type'];
|
||
}
|
||
|
||
if ($forbidComplexExpressions) {
|
||
if ($type && in_array($type, ['subQueryIn', 'subQueryNotIn', 'not'])) {
|
||
throw new Forbidden("SelectManager::checkWhere: Sub-queries are forbidden.");
|
||
}
|
||
}
|
||
|
||
if ($attribute && $forbidComplexExpressions) {
|
||
if (strpos($attribute, '.') !== false || strpos($attribute, ':')) {
|
||
throw new Forbidden("SelectManager::checkWhere: Complex expressions are forbidden.");
|
||
}
|
||
}
|
||
|
||
if ($attribute && $checkWherePermission) {
|
||
$argumentList = QueryComposer::getAllAttributesFromComplexExpression($attribute);
|
||
foreach ($argumentList as $argument) {
|
||
$this->checkWhereArgument($argument, $type);
|
||
}
|
||
}
|
||
|
||
if (!empty($w['value']) && is_array($w['value'])) {
|
||
$this->checkWhere($w['value'], $checkWherePermission, $forbidComplexExpressions);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected function checkWhereArgument($attribute, $type)
|
||
{
|
||
$entityType = $this->getEntityType();
|
||
|
||
if (strpos($attribute, '.')) {
|
||
list($link, $attribute) = explode('.', $attribute);
|
||
if (!$this->getSeed()->hasRelation($link)) {
|
||
// TODO allow alias
|
||
throw new Forbidden("SelectManager::checkWhere: Unknown relation '{$link}' in where.");
|
||
}
|
||
$entityType = $this->getSeed()->getRelationParam($link, RelationParam::ENTITY);
|
||
if (!$entityType) {
|
||
throw new Forbidden("SelectManager::checkWhere: Bad relation.");
|
||
}
|
||
if (!$this->getAcl()->checkScope($entityType)) {
|
||
throw new Forbidden();
|
||
}
|
||
}
|
||
|
||
if ($type && in_array($type, ['isLinked', 'isNotLinked', 'linkedWith', 'notLinkedWith', 'isUserFromTeams'])) {
|
||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenFieldList($entityType))) {
|
||
throw new Forbidden();
|
||
}
|
||
if (
|
||
$this->getSeed()->hasRelation($attribute)
|
||
&&
|
||
in_array($attribute, $this->getAcl()->getScopeForbiddenLinkList($entityType))
|
||
) {
|
||
throw new Forbidden();
|
||
}
|
||
} else {
|
||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenAttributeList($entityType))) {
|
||
throw new Forbidden();
|
||
}
|
||
}
|
||
}
|
||
|
||
public function getUserTimeZone() : string
|
||
{
|
||
if (empty($this->userTimeZone)) {
|
||
$preferences = $this->getEntityManager()->getEntity('Preferences', $this->getUser()->getId());
|
||
if ($preferences) {
|
||
$timeZone = $preferences->get('timeZone');
|
||
$this->userTimeZone = $timeZone;
|
||
} else {
|
||
$this->userTimeZone = 'UTC';
|
||
}
|
||
|
||
if (!$this->userTimeZone) {
|
||
$this->userTimeZone = 'UTC';
|
||
}
|
||
}
|
||
|
||
return $this->userTimeZone;
|
||
}
|
||
|
||
public function transformDateTimeWhereItem(array $item) : array
|
||
{
|
||
$format = 'Y-m-d H:i:s';
|
||
|
||
$attribute = $item['attribute'] ?? null;
|
||
$value = $item['value'] ?? null;
|
||
$timeZone = $item['timeZone'] ?? 'UTC';
|
||
$type = $item['type'] ?? null;
|
||
|
||
// for backward compatibility
|
||
if (!$attribute && isset($item['field'])) {
|
||
$attribute = $item['field'];
|
||
}
|
||
|
||
if (!$attribute) {
|
||
throw new BadRequest("Bad datetime where item, empty 'attribute'.");
|
||
}
|
||
|
||
if (!$type) {
|
||
throw new BadRequest("Bad datetime where item, empty 'type'.");
|
||
}
|
||
|
||
if (empty($value) && in_array($type, ['on', 'before', 'after'])) {
|
||
return [];
|
||
}
|
||
|
||
$where = [
|
||
'attribute' => $attribute,
|
||
];
|
||
|
||
$dt = new DateTime('now', new DateTimeZone($timeZone));
|
||
|
||
switch ($type) {
|
||
case 'today':
|
||
$where['type'] = 'between';
|
||
$dt->setTime(0, 0, 0);
|
||
$dtTo = clone $dt;
|
||
$dtTo->modify('+1 day -1 second');
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$from = $dt->format($format);
|
||
$to = $dtTo->format($format);
|
||
$where['value'] = [$from, $to];
|
||
break;
|
||
|
||
case 'past':
|
||
$where['type'] = 'before';
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'future':
|
||
$where['type'] = 'after';
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'lastSevenDays':
|
||
$where['type'] = 'between';
|
||
|
||
$dtFrom = clone $dt;
|
||
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$to = $dt->format($format);
|
||
|
||
$dtFrom->modify('-7 day');
|
||
$dtFrom->setTime(0, 0, 0);
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
|
||
$from = $dtFrom->format($format);
|
||
|
||
$where['value'] = [$from, $to];
|
||
break;
|
||
|
||
case 'lastXDays':
|
||
$where['type'] = 'between';
|
||
|
||
$dtFrom = clone $dt;
|
||
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$to = $dt->format($format);
|
||
|
||
$number = strval(intval($item['value']));
|
||
$dtFrom->modify('-'.$number.' day');
|
||
$dtFrom->setTime(0, 0, 0);
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
|
||
$from = $dtFrom->format($format);
|
||
|
||
$where['value'] = [$from, $to];
|
||
break;
|
||
|
||
case 'nextXDays':
|
||
$where['type'] = 'between';
|
||
|
||
$dtTo = clone $dt;
|
||
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$from = $dt->format($format);
|
||
|
||
$number = strval(intval($item['value']));
|
||
$dtTo->modify('+'.$number.' day');
|
||
$dtTo->setTime(24, 59, 59);
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
|
||
$to = $dtTo->format($format);
|
||
|
||
$where['value'] = [$from, $to];
|
||
break;
|
||
|
||
case 'olderThanXDays':
|
||
$where['type'] = 'before';
|
||
$number = strval(intval($item['value']));
|
||
$dt->modify('-'.$number.' day');
|
||
$dt->setTime(0, 0, 0);
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'afterXDays':
|
||
$where['type'] = 'after';
|
||
$number = strval(intval($item['value']));
|
||
$dt->modify('+'.$number.' day');
|
||
$dt->setTime(0, 0, 0);
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'on':
|
||
$where['type'] = 'between';
|
||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||
$dtTo = clone $dt;
|
||
if (strlen($value) <= 10) {
|
||
$dtTo->modify('+1 day -1 second');
|
||
}
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$from = $dt->format($format);
|
||
$to = $dtTo->format($format);
|
||
$where['value'] = [$from, $to];
|
||
break;
|
||
|
||
case 'before':
|
||
$where['type'] = 'before';
|
||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'after':
|
||
$where['type'] = 'after';
|
||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||
if (strlen($value) <= 10)
|
||
$dt->modify('+1 day -1 second');
|
||
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = $dt->format($format);
|
||
break;
|
||
|
||
case 'between':
|
||
$where['type'] = 'between';
|
||
if (is_array($value)) {
|
||
$dt = new DateTime($value[0], new DateTimeZone($timeZone));
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
$from = $dt->format($format);
|
||
|
||
$dt = new DateTime($value[1], new DateTimeZone($timeZone));
|
||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||
if (strlen($value[1]) <= 10)
|
||
$dt->modify('+1 day -1 second');
|
||
$to = $dt->format($format);
|
||
|
||
$where['value'] = [$from, $to];
|
||
}
|
||
break;
|
||
|
||
case 'currentMonth':
|
||
case 'lastMonth':
|
||
case 'nextMonth':
|
||
$where['type'] = 'between';
|
||
$dtFrom = new DateTime('now', new DateTimeZone($timeZone));
|
||
$dtFrom = $dt->modify('first day of this month')->setTime(0, 0, 0);
|
||
|
||
if ($type == 'lastMonth') {
|
||
$dtFrom->modify('-1 month');
|
||
} else if ($type == 'nextMonth') {
|
||
$dtFrom->modify('+1 month');
|
||
}
|
||
|
||
$dtTo = clone $dtFrom;
|
||
$dtTo->modify('+1 month');
|
||
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
|
||
$where['value'] = [$dtFrom->format($format), $dtTo->format($format)];
|
||
break;
|
||
|
||
case 'currentQuarter':
|
||
case 'lastQuarter':
|
||
$where['type'] = 'between';
|
||
$dt = new DateTime('now', new DateTimeZone($timeZone));
|
||
$quarter = ceil($dt->format('m') / 3);
|
||
|
||
$dtFrom = clone $dt;
|
||
$dtFrom->modify('first day of January this year')->setTime(0, 0, 0);
|
||
|
||
if ($type === 'lastQuarter') {
|
||
$quarter--;
|
||
if ($quarter == 0) {
|
||
$quarter = 4;
|
||
$dtFrom->modify('-1 year');
|
||
}
|
||
}
|
||
|
||
$dtFrom->add(new DateInterval('P'.(($quarter - 1) * 3).'M'));
|
||
$dtTo = clone $dtFrom;
|
||
$dtTo->add(new DateInterval('P3M'));
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = [
|
||
$dtFrom->format($format),
|
||
$dtTo->format($format)
|
||
];
|
||
break;
|
||
|
||
case 'currentYear':
|
||
case 'lastYear':
|
||
$where['type'] = 'between';
|
||
$dtFrom = new DateTime('now', new DateTimeZone($timeZone));
|
||
$dtFrom->modify('first day of January this year')->setTime(0, 0, 0);
|
||
if ($type == 'lastYear') {
|
||
$dtFrom->modify('-1 year');
|
||
}
|
||
$dtTo = clone $dtFrom;
|
||
$dtTo = $dtTo->modify('+1 year');
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = [
|
||
$dtFrom->format($format),
|
||
$dtTo->format($format)
|
||
];
|
||
break;
|
||
|
||
case 'currentFiscalYear':
|
||
case 'lastFiscalYear':
|
||
$where['type'] = 'between';
|
||
$dtToday = new DateTime('now', new DateTimeZone($timeZone));
|
||
$dt = clone $dtToday;
|
||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months')->setTime(0, 0, 0);
|
||
if (intval($dtToday->format('m')) < $fiscalYearShift + 1) {
|
||
$dt->modify('-1 year');
|
||
}
|
||
if ($type === 'lastFiscalYear') {
|
||
$dt->modify('-1 year');
|
||
}
|
||
$dtFrom = clone $dt;
|
||
$dtTo = clone $dt;
|
||
$dtTo = $dtTo->modify('+1 year');
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = [
|
||
$dtFrom->format($format),
|
||
$dtTo->format($format)
|
||
];
|
||
break;
|
||
|
||
case 'currentFiscalQuarter':
|
||
case 'lastFiscalQuarter':
|
||
$where['type'] = 'between';
|
||
$dtToday = new DateTime('now', new DateTimeZone($timeZone));
|
||
$dt = clone $dtToday;
|
||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months')->setTime(0, 0, 0);
|
||
$month = intval($dtToday->format('m'));
|
||
$quarterShift = floor(($month - $fiscalYearShift - 1) / 3);
|
||
if ($quarterShift) {
|
||
if ($quarterShift >= 0) {
|
||
$dt->add(new DateInterval('P'.($quarterShift * 3).'M'));
|
||
} else {
|
||
$quarterShift *= -1;
|
||
$dt->sub(new DateInterval('P'.($quarterShift * 3).'M'));
|
||
}
|
||
}
|
||
if ($type === 'lastFiscalQuarter') {
|
||
$dt->modify('-3 months');
|
||
}
|
||
$dtFrom = clone $dt;
|
||
$dtTo = clone $dt;
|
||
$dtTo = $dtTo->modify('+3 months');
|
||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||
$where['value'] = [
|
||
$dtFrom->format($format),
|
||
$dtTo->format($format)
|
||
];
|
||
break;
|
||
|
||
default:
|
||
$where['type'] = $type;
|
||
}
|
||
|
||
$where['originalType'] = $type;
|
||
|
||
return $where;
|
||
}
|
||
|
||
public function convertDateTimeWhere(array $item) : ?array
|
||
{
|
||
$where = $this->transformDateTimeWhereItem($item);
|
||
$result = $this->getWherePart($where);
|
||
return $result;
|
||
}
|
||
|
||
protected function getWherePart(array $item, array &$result = []) : array
|
||
{
|
||
$type = $item['type'] ?? null;
|
||
$value = $item['value'] ?? null;
|
||
$attribute = $item['attribute'] ?? null;
|
||
|
||
// for backward compatibility
|
||
if (!$attribute && !empty($item['field'])) {
|
||
$attribute = $item['field'];
|
||
}
|
||
|
||
if ($attribute && !is_string($attribute)) {
|
||
throw new BadRequest("Bad 'attribute' in where item.");
|
||
}
|
||
|
||
if (!empty($item['dateTime'])) {
|
||
return $this->convertDateTimeWhere($item);
|
||
}
|
||
|
||
if (!$type) {
|
||
throw new BadRequest("No 'type' in where item.");
|
||
}
|
||
|
||
if ($attribute && $type) {
|
||
$methodName = 'getWherePart' . ucfirst($attribute) . ucfirst($type);
|
||
if (method_exists($this, $methodName)) {
|
||
return $this->$methodName($value, $result);
|
||
}
|
||
}
|
||
|
||
$part = [];
|
||
|
||
switch ($type) {
|
||
case 'or':
|
||
case 'and':
|
||
if (!is_array($value)) break;
|
||
|
||
$sqWhereClause = [];
|
||
foreach ($value as $sqWhereItem) {
|
||
$sqWherePart = $this->getWherePart($sqWhereItem, $result);
|
||
foreach ($sqWherePart as $left => $right) {
|
||
if (!empty($right) || is_null($right) || $right === '' || $right === 0 || $right === false) {
|
||
$sqWhereClause[] = [$left => $right];
|
||
}
|
||
}
|
||
}
|
||
$part[strtoupper($type)] = $sqWhereClause;
|
||
|
||
break;
|
||
|
||
case 'not':
|
||
case 'subQueryNotIn':
|
||
case 'subQueryIn':
|
||
if (!is_array($value)) break;
|
||
|
||
$sqWhereClause = [];
|
||
$sqResult = $this->getEmptySelectParams();
|
||
foreach ($value as $sqWhereItem) {
|
||
$sqWherePart = $this->getWherePart($sqWhereItem, $sqResult);
|
||
foreach ($sqWherePart as $left => $right) {
|
||
if (!empty($right) || is_null($right) || $right === '' || $right === 0 || $right === false) {
|
||
$sqWhereClause[] = [$left => $right];
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->applyLeftJoinsFromWhere($value, $sqResult);
|
||
$key = $type === 'subQueryIn' ? 'id=s' : 'id!=s';
|
||
$part[$key] = [
|
||
'selectParams' => [
|
||
'select' => ['id'],
|
||
'whereClause' => $sqWhereClause,
|
||
'leftJoins' => $sqResult['leftJoins'] ?? [],
|
||
'joins' => $sqResult['joins'] ?? [],
|
||
]
|
||
];
|
||
|
||
break;
|
||
|
||
case 'expression':
|
||
$key = $attribute;
|
||
if (substr($key, -1) !== ':') $key .= ':';
|
||
$part[$key] = null;
|
||
break;
|
||
|
||
case 'like':
|
||
$part[$attribute . '*'] = $value;
|
||
break;
|
||
|
||
case 'notLike':
|
||
$part[$attribute . '!*'] = $value;
|
||
break;
|
||
|
||
case 'equals':
|
||
case 'on':
|
||
$part[$attribute . '='] = $value;
|
||
break;
|
||
|
||
case 'startsWith':
|
||
$part[$attribute . '*'] = $value . '%';
|
||
break;
|
||
|
||
case 'endsWith':
|
||
$part[$attribute . '*'] = '%' . $value;
|
||
break;
|
||
|
||
case 'contains':
|
||
$part[$attribute . '*'] = '%' . $value . '%';
|
||
break;
|
||
|
||
case 'notContains':
|
||
$part[$attribute . '!*'] = '%' . $value . '%';
|
||
break;
|
||
|
||
case 'notEquals':
|
||
case 'notOn':
|
||
$part[$attribute . '!='] = $value;
|
||
break;
|
||
|
||
case 'greaterThan':
|
||
case 'after':
|
||
$part[$attribute . '>'] = $value;
|
||
break;
|
||
|
||
case 'lessThan':
|
||
case 'before':
|
||
$part[$attribute . '<'] = $value;
|
||
break;
|
||
|
||
case 'greaterThanOrEquals':
|
||
$part[$attribute . '>='] = $value;
|
||
break;
|
||
|
||
case 'lessThanOrEquals':
|
||
$part[$attribute . '<='] = $value;
|
||
break;
|
||
|
||
case 'in':
|
||
$part[$attribute . '='] = $value;
|
||
break;
|
||
|
||
case 'notIn':
|
||
$part[$attribute . '!='] = $value;
|
||
break;
|
||
|
||
case 'isNull':
|
||
$part[$attribute . '='] = null;
|
||
break;
|
||
|
||
case 'isNotNull':
|
||
case 'ever':
|
||
$part[$attribute . '!='] = null;
|
||
break;
|
||
|
||
case 'isTrue':
|
||
$part[$attribute . '='] = true;
|
||
break;
|
||
|
||
case 'isFalse':
|
||
$part[$attribute . '='] = false;
|
||
break;
|
||
|
||
case 'today':
|
||
$part[$attribute . '='] = date('Y-m-d');
|
||
break;
|
||
|
||
case 'past':
|
||
$part[$attribute . '<'] = date('Y-m-d');
|
||
break;
|
||
|
||
case 'future':
|
||
$part[$attribute . '>='] = date('Y-m-d');
|
||
break;
|
||
|
||
case 'lastSevenDays':
|
||
$dt1 = new DateTime();
|
||
$dt2 = clone $dt1;
|
||
$dt2->modify('-7 days');
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt2->format('Y-m-d'),
|
||
$attribute . '<=' => $dt1->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'lastXDays':
|
||
$dt1 = new DateTime();
|
||
$dt2 = clone $dt1;
|
||
$number = strval(intval($value));
|
||
|
||
$dt2->modify('-'.$number.' days');
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt2->format('Y-m-d'),
|
||
$attribute . '<=' => $dt1->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'nextXDays':
|
||
$dt1 = new DateTime();
|
||
$dt2 = clone $dt1;
|
||
$number = strval(intval($value));
|
||
$dt2->modify('+'.$number.' days');
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt1->format('Y-m-d'),
|
||
$attribute . '<=' => $dt2->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'olderThanXDays':
|
||
$dt1 = new DateTime();
|
||
$number = strval(intval($value));
|
||
$dt1->modify('-'.$number.' days');
|
||
$part[$attribute . '<'] = $dt1->format('Y-m-d');
|
||
break;
|
||
|
||
case 'afterXDays':
|
||
$dt1 = new DateTime();
|
||
$number = strval(intval($value));
|
||
$dt1->modify('+'.$number.' days');
|
||
$part[$attribute . '>'] = $dt1->format('Y-m-d');
|
||
break;
|
||
|
||
case 'currentMonth':
|
||
$dt = new DateTime();
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->modify('first day of this month')->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'lastMonth':
|
||
$dt = new DateTime();
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->modify('first day of last month')->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'nextMonth':
|
||
$dt = new DateTime();
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->modify('first day of next month')->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'currentQuarter':
|
||
$dt = new DateTime();
|
||
$quarter = ceil($dt->format('m') / 3);
|
||
$dt->modify('first day of January this year');
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->add(new DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P3M'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'lastQuarter':
|
||
$dt = new DateTime();
|
||
$quarter = ceil($dt->format('m') / 3);
|
||
$dt->modify('first day of January this year');
|
||
$quarter--;
|
||
if ($quarter == 0) {
|
||
$quarter = 4;
|
||
$dt->modify('-1 year');
|
||
}
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->add(new DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P3M'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'currentYear':
|
||
$dt = new DateTime();
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->modify('first day of January this year')->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'lastYear':
|
||
$dt = new DateTime();
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->modify('first day of January last year')->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
||
];
|
||
break;
|
||
|
||
case 'currentFiscalYear':
|
||
case 'lastFiscalYear':
|
||
$dtToday = new DateTime();
|
||
$dt = new DateTime();
|
||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months');
|
||
if (intval($dtToday->format('m')) < $fiscalYearShift + 1) {
|
||
$dt->modify('-1 year');
|
||
}
|
||
if ($type === 'lastFiscalYear') {
|
||
$dt->modify('-1 year');
|
||
}
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d')
|
||
];
|
||
break;
|
||
|
||
case 'currentFiscalQuarter':
|
||
case 'lastFiscalQuarter':
|
||
$dtToday = new DateTime();
|
||
$dt = new DateTime();
|
||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months');
|
||
$month = intval($dtToday->format('m'));
|
||
$quarterShift = floor(($month - $fiscalYearShift - 1) / 3);
|
||
if ($quarterShift) {
|
||
if ($quarterShift >= 0) {
|
||
$dt->add(new DateInterval('P'.($quarterShift * 3).'M'));
|
||
} else {
|
||
$quarterShift *= -1;
|
||
$dt->sub(new DateInterval('P'.($quarterShift * 3).'M'));
|
||
}
|
||
}
|
||
if ($type === 'lastFiscalQuarter') {
|
||
$dt->modify('-3 months');
|
||
}
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $dt->format('Y-m-d'),
|
||
$attribute . '<' => $dt->add(new DateInterval('P3M'))->format('Y-m-d')
|
||
];
|
||
break;
|
||
|
||
case 'between':
|
||
if (is_array($value)) {
|
||
$part['AND'] = [
|
||
$attribute . '>=' => $value[0],
|
||
$attribute . '<=' => $value[1],
|
||
];
|
||
}
|
||
break;
|
||
|
||
case 'columnLike':
|
||
case 'columnIn':
|
||
case 'columnIsNull':
|
||
case 'columnNotIn':
|
||
$link = $this->getMetadata()->get(['entityDefs', $this->entityType, 'fields', $attribute, 'link']);
|
||
$column = $this->getMetadata()->get(['entityDefs', $this->entityType, 'fields', $attribute, 'column']);
|
||
$alias = $link . 'Filter' . strval(rand(10000, 99999));
|
||
$this->setDistinct(true, $result);
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
$columnKey = $alias . 'Middle.' . $column;
|
||
if ($type === 'columnIn') {
|
||
$part[$columnKey] = $value;
|
||
} else if ($type === 'columnNotIn') {
|
||
$part[$columnKey . '!='] = $value;
|
||
} else if ($type === 'columnIsNull') {
|
||
$part[$columnKey] = null;
|
||
} else if ($type === 'columnIsNotNull') {
|
||
$part[$columnKey . '!='] = null;
|
||
} else if ($type === 'columnLike') {
|
||
$part[$columnKey . '*'] = $value;
|
||
} else if ($type === 'columnStartsWith') {
|
||
$part[$columnKey . '*'] = $value . '%';
|
||
} else if ($type === 'columnEndsWith') {
|
||
$part[$columnKey . '*'] = '%' . $value;
|
||
} else if ($type === 'columnContains') {
|
||
$part[$columnKey . '*'] = '%' . $value . '%';
|
||
} else if ($type === 'columnEquals') {
|
||
$part[$columnKey . '='] = $value;
|
||
} else if ($type === 'columnNotEquals') {
|
||
$part[$columnKey . '!='] = $value;
|
||
}
|
||
break;
|
||
|
||
case 'isNotLinked':
|
||
$part['id!=s'] = [
|
||
'selectParams' => [
|
||
'select' => ['id'],
|
||
'joins' => [$attribute],
|
||
]
|
||
];
|
||
break;
|
||
|
||
case 'isLinked':
|
||
if (!$result) break;
|
||
$alias = $attribute . 'IsLinkedFilter' . strval(rand(10000, 99999));
|
||
$part[$alias . '.id!='] = null;
|
||
$this->setDistinct(true, $result);
|
||
$this->addLeftJoin([$attribute, $alias], $result);
|
||
break;
|
||
|
||
case 'linkedWith':
|
||
$seed = $this->getSeed();
|
||
$link = $attribute;
|
||
if (!$seed->hasRelation($link)) break;
|
||
|
||
$alias = $link . 'Filter' . strval(rand(10000, 99999));
|
||
|
||
if (is_null($value) || !$value && !is_array($value)) break;
|
||
|
||
$relationType = $seed->getRelationType($link);
|
||
|
||
if ($relationType == 'manyMany') {
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
$midKeys = $seed->getRelationParam($link, 'midKeys');
|
||
|
||
if (!empty($midKeys)) {
|
||
$key = $midKeys[1];
|
||
$part[$alias . 'Middle.' . $key] = $value;
|
||
}
|
||
|
||
} else if ($relationType == 'hasMany') {
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
|
||
$part[$alias . '.id'] = $value;
|
||
|
||
} else if ($relationType == 'belongsTo') {
|
||
$key = $seed->getRelationParam($link, 'key');
|
||
if (!empty($key)) {
|
||
$part[$key] = $value;
|
||
}
|
||
|
||
} else if ($relationType == 'hasOne') {
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
$part[$alias . '.id'] = $value;
|
||
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
break;
|
||
|
||
case 'notLinkedWith':
|
||
$seed = $this->getSeed();
|
||
$link = $attribute;
|
||
if (!$seed->hasRelation($link)) break;
|
||
|
||
if (is_null($value)) break;
|
||
|
||
$relationType = $seed->getRelationType($link);
|
||
|
||
$alias = $link . 'NotLinkedFilter' . strval(rand(10000, 99999));
|
||
|
||
if ($relationType == 'manyMany') {
|
||
$key = $seed->getRelationParam($link, 'midKeys')[1];
|
||
|
||
$this->addLeftJoin(
|
||
[
|
||
$link, $alias, [$key => $value],
|
||
],
|
||
$result
|
||
);
|
||
|
||
$part[$alias . 'Middle.' . $key] = null;
|
||
|
||
} else if ($relationType == 'hasMany') {
|
||
$this->addLeftJoin(
|
||
[
|
||
$link, $alias, ['id' => $value]
|
||
],
|
||
$result
|
||
);
|
||
|
||
$part[$alias . '.id'] = null;
|
||
|
||
} else if ($relationType == 'belongsTo') {
|
||
$key = $seed->getRelationParam($link, 'key');
|
||
|
||
if (!empty($key)) {
|
||
$part[$key . '!='] = $value;
|
||
}
|
||
|
||
} else if ($relationType == 'hasOne') {
|
||
$this->addLeftJoin([$link, $alias], $result);
|
||
|
||
$part[$alias . '.id!='] = $value;
|
||
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
$this->setDistinct(true, $result);
|
||
|
||
break;
|
||
|
||
case 'arrayAnyOf':
|
||
case 'arrayNoneOf':
|
||
case 'arrayIsEmpty':
|
||
case 'arrayIsNotEmpty':
|
||
case 'arrayAllOf':
|
||
if (!$result) break;
|
||
|
||
$arrayValueAlias = 'arrayFilter' . rand(10000, 99999);
|
||
$arrayAttribute = $attribute;
|
||
$arrayEntityType = $this->getEntityType();
|
||
$idPart = 'id';
|
||
|
||
$seed = $this->getSeed();
|
||
|
||
if (strpos($attribute, '.') > 0 || $seed->getAttributeType($attribute) === AttributeType::FOREIGN) {
|
||
if ($seed->getAttributeType($attribute) === AttributeType::FOREIGN) {
|
||
$arrayAttributeLink = $seed->getAttributeParam($attribute, 'relation');
|
||
$arrayAttribute = $seed->getAttributeParam($attribute, 'foreign');
|
||
} else {
|
||
list($arrayAttributeLink, $arrayAttribute) = explode('.', $attribute);
|
||
}
|
||
|
||
$arrayEntityType = $seed->getRelationParam($arrayAttributeLink, RelationParam::ENTITY);
|
||
|
||
$arrayLinkAlias = $arrayAttributeLink . 'Filter' . rand(10000, 99999);
|
||
$idPart = $arrayLinkAlias . '.id';
|
||
|
||
$this->addLeftJoin([$arrayAttributeLink, $arrayLinkAlias], $result);
|
||
|
||
$relationType = $seed->getRelationType($arrayAttributeLink);
|
||
if ($relationType === 'manyMany' || $relationType === 'hasMany') {
|
||
$this->setDistinct(true, $result);
|
||
}
|
||
}
|
||
|
||
if ($type === 'arrayAnyOf') {
|
||
if (is_null($value) || !$value && !is_array($value)) break;
|
||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||
$arrayValueAlias . '.entityId:' => $idPart,
|
||
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||
]], $result);
|
||
$part[$arrayValueAlias . '.value'] = $value;
|
||
|
||
$this->setDistinct(true, $result);
|
||
} else if ($type === 'arrayNoneOf') {
|
||
if (is_null($value) || !$value && !is_array($value)) break;
|
||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||
$arrayValueAlias . '.entityId:' => $idPart,
|
||
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
||
$arrayValueAlias . '.attribute' => $arrayAttribute,
|
||
$arrayValueAlias . '.value=' => $value
|
||
]], $result);
|
||
$part[$arrayValueAlias . '.id'] = null;
|
||
|
||
$this->setDistinct(true, $result);
|
||
} else if ($type === 'arrayIsEmpty') {
|
||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||
$arrayValueAlias . '.entityId:' => $idPart,
|
||
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||
]], $result);
|
||
$part[$arrayValueAlias . '.id'] = null;
|
||
|
||
$this->setDistinct(true, $result);
|
||
} else if ($type === 'arrayIsNotEmpty') {
|
||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||
$arrayValueAlias . '.entityId:' => $idPart,
|
||
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||
]], $result);
|
||
$part[$arrayValueAlias . '.id!='] = null;
|
||
|
||
$this->setDistinct(true, $result);
|
||
} else if ($type === 'arrayAllOf') {
|
||
if (is_null($value) || !$value && !is_array($value)) break;
|
||
|
||
if (!is_array($value)) {
|
||
$value = [$value];
|
||
}
|
||
|
||
foreach ($value as $arrayValue) {
|
||
$part[] = [
|
||
$idPart .'=s' => [
|
||
'entityType' => 'ArrayValue',
|
||
'selectParams' => [
|
||
'select' => ['entityId'],
|
||
'whereClause' => [
|
||
'value' => $arrayValue,
|
||
'attribute' => $arrayAttribute,
|
||
'entityType' => $arrayEntityType,
|
||
],
|
||
],
|
||
]
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $part;
|
||
}
|
||
|
||
/**
|
||
* Apply an order to select parameters.
|
||
*/
|
||
public function applyOrder(string $sortBy, $desc, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->order($sortBy, $desc, $result);
|
||
}
|
||
|
||
/**
|
||
* Apply a limit to select parameters.
|
||
*/
|
||
public function applyLimit(?int $offset, ?int $maxSize, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->limit($offset, $maxSize, $result);
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function hasInheritedAccessMethod() : bool
|
||
{
|
||
$method = new ReflectionMethod($this, 'access');
|
||
|
||
return $method->getDeclaringClass()->getName() !== SelectManager::class;
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function applyAccessToQueryBuilder(OrmSelectBuilder $queryBuilder)
|
||
{
|
||
$result = $queryBuilder->build()->getRaw();
|
||
|
||
$this->access($result);
|
||
|
||
$queryBuilder->setRawParams($result);
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function hasInheritedAccessFilterMethod(string $filterName) : bool
|
||
{
|
||
if (
|
||
$this->metadata->get(
|
||
['selectDefs', $this->entityType, 'accessControlFilterClassNameMap', $filterName]
|
||
)
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
$methodName = 'access' . ucfirst($filterName);
|
||
|
||
if (!method_exists($this, $methodName)) {
|
||
return false;
|
||
}
|
||
|
||
$method = new ReflectionMethod($this, $methodName);
|
||
|
||
return $method->getDeclaringClass()->getName() !== SelectManager::class;
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function applyAccessFilterToQueryBuilder(OrmSelectBuilder $queryBuilder, string $filterName)
|
||
{
|
||
$methodName = 'access' . ucfirst($filterName);
|
||
|
||
$result = $queryBuilder->build()->getRaw();
|
||
|
||
$this->$methodName($result);
|
||
|
||
$queryBuilder->setRawParams($result);
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function hasBoolFilter(string $filter) : bool
|
||
{
|
||
$method = 'boolFilter' . ucfirst($filter);
|
||
|
||
return method_exists($this, $method);
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function applyBoolFilterToQueryBuilder(OrmSelectBuilder $queryBuilder, string $filter) : array
|
||
{
|
||
$result = $queryBuilder->build()->getRaw();
|
||
|
||
$method = 'boolFilter' . ucfirst($filter);
|
||
|
||
if (!method_exists($this, $method)) {
|
||
throw new BadRequest("Bool filter '{$filter}' does not exist.");
|
||
}
|
||
|
||
$rawWhereClause = $this->$method($result) ?? [];
|
||
|
||
$queryBuilder->setRawParams($result);
|
||
|
||
return $rawWhereClause;
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function hasPrimaryFilter(string $filter) : bool
|
||
{
|
||
if (
|
||
method_exists($this, 'filter' . ucfirst($filter))
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
if (
|
||
$this->getMetadata()->get(
|
||
['entityDefs', $this->entityType, 'collection', 'filters', $filter, 'className']
|
||
)
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Fallback for backward compatibility.
|
||
*/
|
||
public function applyPrimaryFilterToQueryBuilder(OrmSelectBuilder $queryBuilder, string $filter)
|
||
{
|
||
$result = $queryBuilder->build()->getRaw();
|
||
|
||
$this->applyPrimaryFilter($filter, $result);
|
||
|
||
$queryBuilder->setRawParams($result);
|
||
}
|
||
|
||
/**
|
||
* Apply a primary filter to select parameters.
|
||
*/
|
||
public function applyPrimaryFilter(string $filter, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
|
||
$method = 'filter' . ucfirst($filter);
|
||
|
||
if (method_exists($this, $method)) {
|
||
$this->$method($result);
|
||
|
||
return;
|
||
}
|
||
|
||
$className = $this->getMetadata()
|
||
->get(['entityDefs', $this->entityType, 'collection', 'filters', $filter, 'className']);
|
||
|
||
if ($className) {
|
||
$impl = $this->getInjectableFactory()->create($className);
|
||
|
||
$impl->applyFilter($this->entityType, $filter, $result, $this);
|
||
|
||
return;
|
||
}
|
||
|
||
$result['whereClause'][] = ['id' => null];
|
||
}
|
||
|
||
public function applyFilter(string $filter, array &$result)
|
||
{
|
||
$this->applyPrimaryFilter($filter, $result);
|
||
}
|
||
|
||
/**
|
||
* Apply a bool filter to select parameters.
|
||
*/
|
||
public function applyBoolFilter(string $filter, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
|
||
$method = 'boolFilter' . ucfirst($filter);
|
||
if (method_exists($this, $method)) {
|
||
$wherePart = $this->$method($result);
|
||
if ($wherePart) {
|
||
$result['whereClause'][] = $wherePart;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Apply a list of bool filters to select parameters.
|
||
*/
|
||
public function applyBoolFilterList(array $filterList, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
|
||
$wherePartList = [];
|
||
|
||
foreach ($filterList as $filter) {
|
||
$method = 'boolFilter' . ucfirst($filter);
|
||
if (method_exists($this, $method)) {
|
||
$wherePart = $this->$method($result);
|
||
if ($wherePart) {
|
||
$wherePartList[] = $wherePart;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (count($wherePartList)) {
|
||
if (count($wherePartList) === 1) {
|
||
$result['whereClause'][] = $wherePartList;
|
||
} else {
|
||
$result['whereClause'][] = ['OR' => $wherePartList];
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Apply a text filter to select parameters.
|
||
*/
|
||
public function applyTextFilter(string $textFilter, array &$result)
|
||
{
|
||
$this->prepareResult($result);
|
||
$this->textFilter($textFilter, $result);
|
||
}
|
||
|
||
public function applyAdditional(array $params, array &$result)
|
||
{
|
||
|
||
}
|
||
|
||
/**
|
||
* Check whether a link is already in JOINs. If an existing join has alias, then the alias is checked, the link is ignored.
|
||
*/
|
||
public function hasJoin($join, array &$result)
|
||
{
|
||
$list = $result['joins'] ?? [];
|
||
|
||
if (in_array($join, $list)) {
|
||
return true;
|
||
}
|
||
|
||
foreach ($list as $item) {
|
||
if (is_array($item) && count($item) > 1) {
|
||
if ($item[1] == $join) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Check whether a link is already in LEFT JOINs. If an existing join has alias, then the alias is checked,
|
||
* the link is ignored.
|
||
*/
|
||
public function hasLeftJoin($leftJoin, array &$result)
|
||
{
|
||
$list = $result['leftJoins'] ?? [];
|
||
|
||
if (in_array($leftJoin, $list)) {
|
||
return true;
|
||
}
|
||
|
||
foreach ($list as $item) {
|
||
if (is_array($item) && count($item) > 1) {
|
||
if ($item[1] == $leftJoin) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Check whether a link is already joined. If an existing join has alias, then the alias is checked, the link is ignored.
|
||
*/
|
||
public function hasLinkJoined($join, array &$result)
|
||
{
|
||
if (in_array($join, $result['joins'])) {
|
||
return true;
|
||
}
|
||
|
||
foreach ($result['joins'] as $item) {
|
||
if (is_array($item) && count($item) > 1) {
|
||
if ($item[0] == $join) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (in_array($join, $result['leftJoins'])) {
|
||
return true;
|
||
}
|
||
|
||
foreach ($result['leftJoins'] as $item) {
|
||
if (is_array($item) && count($item) > 1) {
|
||
if ($item[0] == $join) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Add JOIN.
|
||
*
|
||
* @param string|array $join Format used for array: [link, alias, conditions].
|
||
*/
|
||
public function addJoin($join, array &$result)
|
||
{
|
||
if (empty($result['joins'])) {
|
||
$result['joins'] = [];
|
||
}
|
||
|
||
$alias = $join;
|
||
if (is_array($join)) {
|
||
if (count($join) > 1) {
|
||
$alias = $join[1];
|
||
} else {
|
||
$alias = $join[0];
|
||
}
|
||
}
|
||
foreach ($result['joins'] as $j) {
|
||
$a = $j;
|
||
if (is_array($j)) {
|
||
if (count($j) > 1) {
|
||
$a = $j[1];
|
||
} else {
|
||
$a = $j[0];
|
||
}
|
||
}
|
||
if ($a === $alias) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
$result['joins'][] = $join;
|
||
}
|
||
|
||
/**
|
||
* Add LEFT JOIN.
|
||
*
|
||
* @param string|array $join Format used for array: [link, alias, conditions].
|
||
*/
|
||
public function addLeftJoin($leftJoin, array &$result)
|
||
{
|
||
if (empty($result['leftJoins'])) {
|
||
$result['leftJoins'] = [];
|
||
}
|
||
|
||
$alias = $leftJoin;
|
||
if (is_array($leftJoin)) {
|
||
if (count($leftJoin) > 1) {
|
||
$alias = $leftJoin[1];
|
||
} else {
|
||
$alias = $leftJoin[0];
|
||
}
|
||
}
|
||
foreach ($result['leftJoins'] as $j) {
|
||
$a = $j;
|
||
if (is_array($j)) {
|
||
if (count($j) > 1) {
|
||
$a = $j[1];
|
||
} else {
|
||
$a = $j[0];
|
||
}
|
||
}
|
||
if ($a === $alias) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
$result['leftJoins'][] = $leftJoin;
|
||
}
|
||
|
||
public function setJoinCondition(string $join, $condition, array &$result)
|
||
{
|
||
$result['joinConditions'][$join] = $condition;
|
||
}
|
||
|
||
/**
|
||
* Set DISTINCT.
|
||
*/
|
||
public function setDistinct(bool $distinct, array &$result)
|
||
{
|
||
$result['distinct'] = (bool) $distinct;
|
||
}
|
||
|
||
public function addAndWhere(array $whereClause, array &$result)
|
||
{
|
||
$result['whereClause'][] = $whereClause;
|
||
}
|
||
|
||
public function addOrWhere(array $whereClause, array &$result)
|
||
{
|
||
$result['whereClause'][] = [
|
||
'OR' => $whereClause
|
||
];
|
||
}
|
||
|
||
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
|
||
{
|
||
if (array_key_exists($textFilter, $this->fullTextSearchDataCacheHash)) {
|
||
return $this->fullTextSearchDataCacheHash[$textFilter];
|
||
}
|
||
|
||
if ($this->getConfig()->get('fullTextSearchDisabled')) {
|
||
return null;
|
||
}
|
||
|
||
$result = null;
|
||
|
||
$fieldList = $this->getTextFilterFieldList();
|
||
|
||
if ($isAuxiliaryUse) {
|
||
$textFilter = str_replace('%', '', $textFilter);
|
||
}
|
||
|
||
$fullTextSearchColumnList = $this->getEntityManager()->getMetadata()->get(
|
||
$this->getEntityType(), ['fullTextSearchColumnList']
|
||
);
|
||
|
||
$useFullTextSearch = false;
|
||
|
||
if (
|
||
$this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'collection', 'fullTextSearch'])
|
||
&&
|
||
!empty($fullTextSearchColumnList)
|
||
) {
|
||
$fullTextSearchMinLength = $this->getConfig()->get('fullTextSearchMinLength', self::MIN_LENGTH_FOR_FULL_TEXT_SEARCH);
|
||
if (!$fullTextSearchMinLength) {
|
||
$fullTextSearchMinLength = 0;
|
||
}
|
||
$textFilterWoWildcards = str_replace('*', '', $textFilter);
|
||
if (mb_strlen($textFilterWoWildcards) >= $fullTextSearchMinLength) {
|
||
$useFullTextSearch = true;
|
||
}
|
||
}
|
||
|
||
$fullTextSearchFieldList = [];
|
||
|
||
if ($useFullTextSearch) {
|
||
foreach ($fieldList as $field) {
|
||
if (strpos($field, '.') !== false) {
|
||
continue;
|
||
}
|
||
|
||
$defs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $field], []);
|
||
if (empty($defs['type'])) continue;
|
||
$fieldType = $defs['type'];
|
||
if (!empty($defs['notStorable'])) continue;
|
||
if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue;
|
||
$fullTextSearchFieldList[] = $field;
|
||
}
|
||
if (!count($fullTextSearchFieldList)) {
|
||
$useFullTextSearch = false;
|
||
}
|
||
|
||
if (substr_count($textFilter, '\'') % 2 != 0) {
|
||
$useFullTextSearch = false;
|
||
}
|
||
if (substr_count($textFilter, '"') % 2 != 0) {
|
||
$useFullTextSearch = false;
|
||
}
|
||
}
|
||
|
||
if (empty($fullTextSearchColumnList)) {
|
||
$useFullTextSearch = false;
|
||
}
|
||
|
||
if ($isAuxiliaryUse) {
|
||
if (mb_strpos($textFilter, '@') !== false) {
|
||
$useFullTextSearch = false;
|
||
}
|
||
}
|
||
|
||
if ($useFullTextSearch) {
|
||
$textFilter = str_replace(['(', ')'], '', $textFilter);
|
||
|
||
if (
|
||
$isAuxiliaryUse && mb_strpos($textFilter, '*') === false
|
||
||
|
||
mb_strpos($textFilter, ' ') === false
|
||
&&
|
||
mb_strpos($textFilter, '+') === false
|
||
&&
|
||
mb_strpos($textFilter, '-') === false
|
||
&&
|
||
mb_strpos($textFilter, '*') === false
|
||
) {
|
||
$function = 'MATCH_NATURAL_LANGUAGE';
|
||
} else {
|
||
$function = 'MATCH_BOOLEAN';
|
||
}
|
||
|
||
$textFilter = str_replace('"*', '"', $textFilter);
|
||
$textFilter = str_replace('*"', '"', $textFilter);
|
||
|
||
$textFilter = str_replace('\'', '\'\'', $textFilter);
|
||
|
||
while (strpos($textFilter, '**')) {
|
||
$textFilter = str_replace('**', '*', $textFilter);
|
||
$textFilter = trim($textFilter);
|
||
}
|
||
|
||
while (mb_substr($textFilter, -2) === ' *') {
|
||
$textFilter = mb_substr($textFilter, 0, mb_strlen($textFilter) - 2);
|
||
$textFilter = trim($textFilter);
|
||
}
|
||
|
||
$where = $function . ':(' . implode(', ', $fullTextSearchColumnList) . ', ' . "'{$textFilter}'" . ')';
|
||
|
||
$result = [
|
||
'where' => $where,
|
||
'fieldList' => $fullTextSearchFieldList,
|
||
'columnList' => $fullTextSearchColumnList,
|
||
];
|
||
}
|
||
|
||
$this->fullTextSearchDataCacheHash[$textFilter] = $result;
|
||
|
||
return $result;
|
||
}
|
||
|
||
protected function textFilter($textFilter, array &$result, $noFullText = false)
|
||
{
|
||
$fieldList = $this->getTextFilterFieldList();
|
||
|
||
$group = [];
|
||
|
||
$textFilterContainsMinLength = $this->getConfig()
|
||
->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
|
||
|
||
$fullTextSearchData = null;
|
||
|
||
$forceFullTextSearch = false;
|
||
|
||
$useFullTextSearch = !empty($result['useFullTextSearch']);
|
||
|
||
if (mb_strpos($textFilter, 'ft:') === 0) {
|
||
$textFilter = mb_substr($textFilter, 3);
|
||
$useFullTextSearch = true;
|
||
$forceFullTextSearch = true;
|
||
}
|
||
|
||
$textFilterForFullTextSearch = $textFilter;
|
||
|
||
$skipWidlcards = false;
|
||
|
||
if (mb_strpos($textFilter, '*') !== false) {
|
||
$skipWidlcards = true;
|
||
$textFilter = str_replace('*', '%', $textFilter);
|
||
}
|
||
|
||
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
|
||
|
||
$skipFullTextSearch = false;
|
||
|
||
if (!$forceFullTextSearch) {
|
||
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
|
||
$skipFullTextSearch = true;
|
||
} else if (mb_strpos($textFilterForFullTextSearch, ' *') !== false) {
|
||
$skipFullTextSearch = true;
|
||
}
|
||
}
|
||
|
||
if ($noFullText) {
|
||
$skipFullTextSearch = true;
|
||
}
|
||
|
||
$fullTextSearchData = null;
|
||
|
||
if (!$skipFullTextSearch) {
|
||
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
|
||
}
|
||
|
||
$fullTextGroup = [];
|
||
|
||
$fullTextSearchFieldList = [];
|
||
|
||
if ($fullTextSearchData) {
|
||
if ($this->fullTextRelevanceThreshold) {
|
||
$fullTextGroup[] = [$fullTextSearchData['where'] . '>=' => $this->fullTextRelevanceThreshold];
|
||
} else {
|
||
$fullTextGroup[] = $fullTextSearchData['where'];
|
||
}
|
||
|
||
$fullTextSearchFieldList = $fullTextSearchData['fieldList'];
|
||
|
||
$relevanceExpression = $fullTextSearchData['where'];
|
||
|
||
$fullTextOrderType = $this->fullTextOrderType;
|
||
|
||
$orderTypeMap = [
|
||
'combined' => self::FT_ORDER_COMBINTED,
|
||
'relevance' => self::FT_ORDER_RELEVANCE,
|
||
'original' => self::FT_ORDER_ORIGINAL,
|
||
];
|
||
|
||
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
|
||
|
||
if ($mOrderType) {
|
||
$fullTextOrderType = $orderTypeMap[$mOrderType];
|
||
}
|
||
|
||
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
|
||
$result['orderBy'] = [[$relevanceExpression, 'desc']];
|
||
$result['order'] = null;
|
||
} else {
|
||
if ($fullTextOrderType === self::FT_ORDER_COMBINTED) {
|
||
$relevanceExpression =
|
||
'ROUND:(DIV:(' . $fullTextSearchData['where'] . ','.$this->fullTextOrderRelevanceDivider.'))';
|
||
|
||
if (is_string($result['orderBy'])) {
|
||
$result['orderBy'] = [
|
||
[$relevanceExpression, 'desc'],
|
||
[$result['orderBy'], $result['order'] ?? 'asc'],
|
||
];
|
||
} else if (is_array($result['orderBy'])) {
|
||
$result['orderBy'] = array_merge(
|
||
[[$relevanceExpression, 'desc']],
|
||
$result['orderBy']
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
$result['hasFullTextSearch'] = true;
|
||
}
|
||
|
||
foreach ($fieldList as $field) {
|
||
if ($useFullTextSearch) {
|
||
if (in_array($field, $fullTextSearchFieldList)) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if ($forceFullTextSearch) {
|
||
continue;
|
||
}
|
||
|
||
$seed = $this->getSeed();
|
||
|
||
$attributeType = null;
|
||
|
||
if (strpos($field, '.') !== false) {
|
||
list($link, $foreignField) = explode('.', $field);
|
||
|
||
$foreignEntityType = $seed->getRelationParam($link, 'entity');
|
||
$seed = $this->getEntityManager()->getEntity($foreignEntityType);
|
||
|
||
$this->addLeftJoin($link, $result);
|
||
|
||
if ($seed->getRelationParam($link, 'type') === $seed::HAS_MANY) {
|
||
$this->setDistinct(true, $result);
|
||
}
|
||
|
||
$attributeType = $seed->getAttributeType($foreignField);
|
||
} else {
|
||
$attributeType = $seed->getAttributeType($field);
|
||
|
||
if ($attributeType === AttributeType::FOREIGN) {
|
||
$link = $seed->getAttributeParam($field, 'relation');
|
||
|
||
if ($link) {
|
||
$this->addLeftJoin($link, $result);
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($attributeType === 'int') {
|
||
if (is_numeric($textFilter)) {
|
||
$group[$field] = intval($textFilter);
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
if (!$skipWidlcards) {
|
||
if (
|
||
mb_strlen($textFilter) >= $textFilterContainsMinLength
|
||
&&
|
||
(
|
||
$attributeType == AttributeType::TEXT
|
||
||
|
||
in_array($field, $this->textFilterUseContainsAttributeList)
|
||
||
|
||
$attributeType == AttributeType::VARCHAR && $this->getConfig()->get('textFilterUseContainsForVarchar')
|
||
)
|
||
) {
|
||
$expression = '%' . $textFilter . '%';
|
||
} else {
|
||
$expression = $textFilter . '%';
|
||
}
|
||
} else {
|
||
$expression = $textFilter;
|
||
}
|
||
|
||
if ($fullTextSearchData) {
|
||
if (!$useFullTextSearch) {
|
||
if (in_array($field, $fullTextSearchFieldList)) {
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
$group[$field . '*'] = $expression;
|
||
}
|
||
|
||
if (!$forceFullTextSearch) {
|
||
$this->applyAdditionalToTextFilterGroup($textFilter, $group, $result);
|
||
}
|
||
|
||
if (!empty($fullTextGroup)) {
|
||
$group['AND'] = $fullTextGroup;
|
||
}
|
||
|
||
if (count($group) === 0) {
|
||
$result['whereClause'][] = [
|
||
'id' => null
|
||
];
|
||
}
|
||
|
||
$result['whereClause'][] = [
|
||
'OR' => $group
|
||
];
|
||
}
|
||
|
||
protected function applyAdditionalToTextFilterGroup(string $textFilter, array &$group, array &$result)
|
||
{
|
||
}
|
||
|
||
public function applyAccess(array &$result)
|
||
{
|
||
$result['from'] = $this->entityType;
|
||
|
||
$query = SelectQuery::fromRaw($result);
|
||
|
||
$result = $this->selectBuilderFactory
|
||
->create()
|
||
->clone($query)
|
||
->forUser($this->user)
|
||
->withAccessControlFilter()
|
||
->build()
|
||
->getRaw();
|
||
}
|
||
|
||
protected function boolFilters(array $params, array &$result)
|
||
{
|
||
if (!empty($params['boolFilterList']) && is_array($params['boolFilterList'])) {
|
||
foreach ($params['boolFilterList'] as $filterName) {
|
||
$this->applyBoolFilter($filterName, $result);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected function getBoolFilterWhere(string $filterName)
|
||
{
|
||
$method = 'getBoolFilterWhere' . ucfirst($filterName);
|
||
|
||
if (method_exists($this, $method)) {
|
||
return $this->$method();
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
protected function boolFilterOnlyMy(&$result)
|
||
{
|
||
if (!$this->checkIsPortal()) {
|
||
if ($this->hasAssignedUsersField()) {
|
||
$this->setDistinct(true, $result);
|
||
$this->addLeftJoin(['assignedUsers', 'assignedUsersOnlyMyFilter'], $result);
|
||
$wherePart = [
|
||
'assignedUsersOnlyMyFilter.id' => $this->getUser()->getId()
|
||
];
|
||
} else if ($this->hasAssignedUserField()) {
|
||
$wherePart = [
|
||
'assignedUserId' => $this->getUser()->getId()
|
||
];
|
||
} else {
|
||
$wherePart = [
|
||
'createdById' => $this->getUser()->getId()
|
||
];
|
||
}
|
||
} else {
|
||
$wherePart = [
|
||
'createdById' => $this->getUser()->getId()
|
||
];
|
||
}
|
||
|
||
return $wherePart;
|
||
}
|
||
|
||
protected function boolFilterOnlyMyTeam(&$result)
|
||
{
|
||
$teamIdList = $this->getUser()->getLinkMultipleIdList('teams');
|
||
|
||
if (count($teamIdList) === 0) {
|
||
return [
|
||
'id' => null
|
||
];
|
||
}
|
||
|
||
$this->addLeftJoin(['teams', 'teamsOnlyMyFilter'], $result);
|
||
$this->setDistinct(true, $result);
|
||
return [
|
||
'teamsOnlyMyFilterMiddle.teamId' => $teamIdList
|
||
];
|
||
}
|
||
|
||
protected function filterFollowed(&$result)
|
||
{
|
||
$this->addJoin([
|
||
StreamSubscription::ENTITY_TYPE,
|
||
'subscription',
|
||
[
|
||
'subscription.entityType' => $this->getEntityType(),
|
||
'subscription.entityId=:' => 'id',
|
||
'subscription.userId' => $this->getUser()->getId(),
|
||
]
|
||
], $result);
|
||
}
|
||
|
||
protected function boolFilterFollowed(&$result)
|
||
{
|
||
$this->addLeftJoin([
|
||
StreamSubscription::ENTITY_TYPE,
|
||
'subscription',
|
||
[
|
||
'subscription.entityType' => $this->getEntityType(),
|
||
'subscription.entityId=:' => 'id',
|
||
'subscription.userId' => $this->getUser()->getId(),
|
||
]
|
||
], $result);
|
||
|
||
return ['subscription.id!=' => null];
|
||
}
|
||
|
||
public function mergeSelectParams(array $selectParams1, ?array $selectParams2) : array
|
||
{
|
||
if (!$selectParams2) {
|
||
return $selectParams1;
|
||
}
|
||
if (!isset($selectParams1['whereClause'])) {
|
||
$selectParams1['whereClause'] = [];
|
||
}
|
||
if (!empty($selectParams2['whereClause'])) {
|
||
$selectParams1['whereClause'][] = $selectParams2['whereClause'];
|
||
}
|
||
|
||
if (!isset($selectParams1['havingClause'])) {
|
||
$selectParams1['havingClause'] = [];
|
||
}
|
||
if (!empty($selectParams2['havingClause'])) {
|
||
$selectParams1['havingClause'][] = $selectParams2['havingClause'];
|
||
}
|
||
|
||
|
||
if (!empty($selectParams2['joins'])) {
|
||
foreach ($selectParams2['joins'] as $item) {
|
||
$this->addJoin($item, $selectParams1);
|
||
}
|
||
}
|
||
|
||
if (!empty($selectParams2['leftJoins'])) {
|
||
foreach ($selectParams2['leftJoins'] as $item) {
|
||
if ($this->hasJoin($item, $selectParams1)) {
|
||
continue;
|
||
}
|
||
|
||
$this->addLeftJoin($item, $selectParams1);
|
||
}
|
||
}
|
||
|
||
if (isset($selectParams2['select'])) {
|
||
$selectParams1['select'] = $selectParams2['select'];
|
||
}
|
||
|
||
if (isset($selectParams2['customJoin'])) {
|
||
if (!isset($selectParams1['customJoin'])) {
|
||
$selectParams1['customJoin'] = '';
|
||
}
|
||
$selectParams1['customJoin'] .= ' ' . $selectParams2['customJoin'];
|
||
}
|
||
|
||
if (isset($selectParams2['customWhere'])) {
|
||
if (!isset($selectParams1['customWhere'])) {
|
||
$selectParams1['customWhere'] = '';
|
||
}
|
||
$selectParams1['customWhere'] .= ' ' . $selectParams2['customWhere'];
|
||
}
|
||
|
||
if (isset($selectParams2['additionalSelectColumns'])) {
|
||
if (!isset($selectParams1['additionalSelectColumns'])) {
|
||
$selectParams1['additionalSelectColumns'] = [];
|
||
}
|
||
foreach ($selectParams2['additionalSelectColumns'] as $key => $item) {
|
||
$selectParams1['additionalSelectColumns'][$key] = $item;
|
||
}
|
||
}
|
||
|
||
if (isset($selectParams2['joinConditions'])) {
|
||
if (!isset($selectParams1['joinConditions'])) {
|
||
$selectParams1['joinConditions'] = [];
|
||
}
|
||
foreach ($selectParams2['joinConditions'] as $key => $item) {
|
||
$selectParams1['joinConditions'][$key] = $item;
|
||
}
|
||
}
|
||
|
||
if (isset($selectParams2['orderBy'])) {
|
||
$selectParams1['orderBy'] = $selectParams2['orderBy'];
|
||
}
|
||
if (isset($selectParams2['order'])) {
|
||
$selectParams1['order'] = $selectParams2['order'];
|
||
}
|
||
|
||
if (!empty($selectParams2['distinct'])) {
|
||
$selectParams1['distinct'] = true;
|
||
}
|
||
|
||
return $selectParams1;
|
||
}
|
||
|
||
public function applyLeftJoinsFromWhere($where, array &$result)
|
||
{
|
||
if (!is_array($where)) return;
|
||
|
||
foreach ($where as $item) {
|
||
$this->applyLeftJoinsFromWhereItem($item, $result);
|
||
}
|
||
}
|
||
|
||
public function applyLeftJoinsFromWhereItem($item, array &$result)
|
||
{
|
||
$type = $item['type'] ?? null;
|
||
|
||
if ($type) {
|
||
if (in_array($type, ['subQueryNotIn', 'subQueryIn', 'not'])) return;
|
||
|
||
if (in_array($type, ['or', 'and', 'having'])) {
|
||
$value = $item['value'] ?? null;
|
||
if (!is_array($value)) return;
|
||
foreach ($value as $listItem) {
|
||
$this->applyLeftJoinsFromWhereItem($listItem, $result);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
$attribute = $item['attribute'] ?? null;
|
||
if (!$attribute) return;
|
||
|
||
$this->applyLeftJoinsFromAttribute($attribute, $result);
|
||
}
|
||
|
||
protected function applyLeftJoinsFromAttribute(string $attribute, array &$result)
|
||
{
|
||
if (strpos($attribute, ':') !== false) {
|
||
$argumentList = QueryComposer::getAllAttributesFromComplexExpression($attribute);
|
||
foreach ($argumentList as $argument) {
|
||
$this->applyLeftJoinsFromAttribute($argument, $result);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (strpos($attribute, '.') !== false) {
|
||
list($link, $attribute) = explode('.', $attribute);
|
||
if ($this->getSeed()->hasRelation($link) && !$this->hasLeftJoin($link, $result)) {
|
||
$this->addLeftJoin($link, $result);
|
||
if ($this->getSeed()->getRelationType($link) === Entity::HAS_MANY) {
|
||
$result['distinct'] = true;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
$attributeType = $this->getSeed()->getAttributeType($attribute);
|
||
if ($attributeType === AttributeType::FOREIGN) {
|
||
$relation = $this->getSeed()->getAttributeParam($attribute, 'relation');
|
||
if ($relation) {
|
||
$this->addLeftJoin($relation, $result);
|
||
}
|
||
}
|
||
}
|
||
|
||
public function getSelectAttributeList(array $params) : ?array
|
||
{
|
||
if (array_key_exists('select', $params)) {
|
||
$passedAttributeList = $params['select'];
|
||
} else {
|
||
return null;
|
||
}
|
||
|
||
$seed = $this->getSeed();
|
||
|
||
$attributeList = [];
|
||
if (!in_array(Attribute::ID, $passedAttributeList)) {
|
||
$attributeList[] = Attribute::ID;
|
||
}
|
||
|
||
$aclAttributeList = $this->aclAttributeList;
|
||
if ($this->getUser()->isPortal()) {
|
||
$aclAttributeList = $this->aclPortalAttributeList;
|
||
}
|
||
|
||
foreach ($aclAttributeList as $attribute) {
|
||
if (!in_array($attribute, $passedAttributeList) && $seed->hasAttribute($attribute)) {
|
||
$attributeList[] = $attribute;
|
||
}
|
||
}
|
||
|
||
foreach ($passedAttributeList as $attribute) {
|
||
if (!in_array($attribute, $attributeList) && $seed->hasAttribute($attribute)) {
|
||
$attributeList[] = $attribute;
|
||
}
|
||
}
|
||
|
||
$sortByField = $params['orderBy'] ?? $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
|
||
|
||
if ($sortByField) {
|
||
$sortByAttributeList = $this->getFieldUtil()->getAttributeList($this->getEntityType(), $sortByField);
|
||
foreach ($sortByAttributeList as $attribute) {
|
||
if (!in_array($attribute, $attributeList) && $seed->hasAttribute($attribute)) {
|
||
$attributeList[] = $attribute;
|
||
}
|
||
}
|
||
}
|
||
|
||
$map = $this->selectAttributesDependancyMap;
|
||
|
||
$map = array_merge(
|
||
$map,
|
||
$this->metadata->get(['selectDefs', $this->entityType, 'selectAttributesDependencyMap']) ?? []
|
||
);
|
||
|
||
foreach ($map as $attribute => $dependantAttributeList) {
|
||
if (in_array($attribute, $attributeList)) {
|
||
foreach ($dependantAttributeList as $dependantAttribute) {
|
||
if (!in_array($dependantAttribute, $attributeList)) {
|
||
$attributeList[] = $dependantAttribute;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $attributeList;
|
||
}
|
||
|
||
protected function hasInOrderBy(string $attribute, array &$result)
|
||
{
|
||
$orderBy = $result['orderBy'] ?? null;
|
||
|
||
if (!$orderBy) return false;
|
||
|
||
if (is_string($orderBy)) {
|
||
return $attribute === $orderBy;
|
||
}
|
||
|
||
if (is_array($orderBy)) {
|
||
foreach ($orderBy as $item) {
|
||
if (is_array($item) && count($item)) {
|
||
if ($item[0] === $attribute) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|