mirror of
https://github.com/espocrm/espocrm.git
synced 2026-07-01 08:26:04 +00:00
1457 lines
39 KiB
PHP
1457 lines
39 KiB
PHP
<?php
|
|
/************************************************************************
|
|
* This file is part of EspoCRM.
|
|
*
|
|
* EspoCRM - Open Source CRM application.
|
|
* Copyright (C) 2014-2022 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\Where;
|
|
|
|
use Espo\Core\Select\Where\Item\Type;
|
|
|
|
use Espo\{
|
|
Core\Exceptions\Error,
|
|
ORM\Query\SelectBuilder as QueryBuilder,
|
|
ORM\Query\Part\WhereClause,
|
|
ORM\Query\Part\WhereItem as WhereClauseItem,
|
|
ORM\EntityManager,
|
|
ORM\Entity,
|
|
ORM\Defs as ORMDefs,
|
|
Entities\User,
|
|
Core\Utils\Config,
|
|
Core\Select\Helpers\RandomStringGenerator,
|
|
Core\Utils\Metadata,
|
|
};
|
|
|
|
use DateTime;
|
|
use DateInterval;
|
|
|
|
/**
|
|
* Converts a where item to a where clause (for ORM).
|
|
*/
|
|
class ItemGeneralConverter implements ItemConverter
|
|
{
|
|
private string $entityType;
|
|
|
|
private User $user;
|
|
|
|
private DateTimeItemTransformer $dateTimeItemTransformer;
|
|
|
|
private Scanner $scanner;
|
|
|
|
private ItemConverterFactory $itemConverterFactory;
|
|
|
|
private RandomStringGenerator $randomStringGenerator;
|
|
|
|
private EntityManager $entityManager;
|
|
|
|
private ORMDefs $ormDefs;
|
|
|
|
private Config $config;
|
|
|
|
private Metadata $metadata;
|
|
|
|
public function __construct(
|
|
string $entityType,
|
|
User $user,
|
|
DateTimeItemTransformer $dateTimeItemTransformer,
|
|
Scanner $scanner,
|
|
ItemConverterFactory $itemConverterFactory,
|
|
RandomStringGenerator $randomStringGenerator,
|
|
EntityManager $entityManager,
|
|
ORMDefs $ormDefs,
|
|
Config $config,
|
|
Metadata $metadata
|
|
) {
|
|
$this->entityType = $entityType;
|
|
$this->user = $user;
|
|
$this->dateTimeItemTransformer = $dateTimeItemTransformer;
|
|
$this->scanner = $scanner;
|
|
$this->itemConverterFactory = $itemConverterFactory;
|
|
$this->randomStringGenerator = $randomStringGenerator;
|
|
$this->entityManager = $entityManager;
|
|
$this->ormDefs = $ormDefs;
|
|
$this->config = $config;
|
|
$this->metadata = $metadata;
|
|
}
|
|
|
|
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
|
|
{
|
|
$type = $item->getType();
|
|
$value = $item->getValue();
|
|
$attribute = $item->getAttribute();
|
|
$data = $item->getData();
|
|
|
|
if ($data instanceof Item\Data\DateTime) {
|
|
return $this->convert(
|
|
$queryBuilder,
|
|
$this->dateTimeItemTransformer->transform($item)
|
|
);
|
|
}
|
|
|
|
if (!$type) {
|
|
throw new Error("Bad where item. No 'type'.");
|
|
}
|
|
|
|
if (
|
|
$attribute &&
|
|
$this->itemConverterFactory->has($this->entityType, $attribute, $type)
|
|
) {
|
|
|
|
$converter = $this->itemConverterFactory->create(
|
|
$this->entityType,
|
|
$attribute,
|
|
$type,
|
|
$this->user
|
|
);
|
|
|
|
return $converter->convert($queryBuilder, $item);
|
|
}
|
|
|
|
switch ($type) {
|
|
case Type::OR:
|
|
case Type::AND:
|
|
|
|
return WhereClause::fromRaw(
|
|
$this->groupProcessAndOr($queryBuilder, $type, $attribute, $value)
|
|
);
|
|
|
|
case Type::NOT:
|
|
case Type::SUBQUERY_NOT_IN:
|
|
case Type::SUBQUERY_IN:
|
|
|
|
return WhereClause::fromRaw(
|
|
$this->groupProcessSubQuery($queryBuilder, $type, $attribute, $value)
|
|
);
|
|
}
|
|
|
|
if (!$attribute) {
|
|
throw new Error("Bad where item. No 'attribute'.");
|
|
}
|
|
|
|
switch ($type) {
|
|
case 'columnLike':
|
|
case 'columnIn':
|
|
case 'columnNotIn':
|
|
case 'columnIsNotNull':
|
|
case 'columnEquals':
|
|
case 'columnNotEquals':
|
|
|
|
return WhereClause::fromRaw(
|
|
$this->groupProcessColumn($queryBuilder, $type, $attribute, $value)
|
|
);
|
|
|
|
case Type::ARRAY_ANY_OF:
|
|
case Type::ARRAY_NONE_OF:
|
|
case Type::ARRAY_IS_EMPTY:
|
|
case Type::ARRAY_IS_EMPTY:
|
|
case Type::ARRAY_ALL_OF:
|
|
|
|
return WhereClause::fromRaw(
|
|
$this->groupProcessArray($queryBuilder, $type, $attribute, $value)
|
|
);
|
|
}
|
|
|
|
$methodName = 'process' . ucfirst($type);
|
|
|
|
if (method_exists($this, $methodName)) {
|
|
return WhereClause::fromRaw(
|
|
$this->$methodName($queryBuilder, $attribute, $value)
|
|
);
|
|
}
|
|
|
|
if (!$this->itemConverterFactory->hasForType($type)) {
|
|
throw new Error("Unknown where item type '{$type}'.");
|
|
}
|
|
|
|
$converter = $this->itemConverterFactory->createForType($type, $this->entityType, $this->user);
|
|
|
|
return $converter->convert($queryBuilder, $item);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function groupProcessAndOr(
|
|
QueryBuilder $queryBuilder,
|
|
string $type,
|
|
?string $attribute,
|
|
$value
|
|
): array {
|
|
|
|
if (!is_array($value)) {
|
|
throw new Error("Bad where item.");
|
|
}
|
|
|
|
$whereClause = [];
|
|
|
|
foreach ($value as $item) {
|
|
$subPart = $this->convert($queryBuilder, Item::fromRaw($item))->getRaw();
|
|
|
|
foreach ($subPart as $left => $right) {
|
|
if (!empty($right) || is_null($right) || $right === '' || $right === 0 || $right === false) {
|
|
$whereClause[] = [
|
|
$left => $right,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
strtoupper($type) => $whereClause,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function groupProcessSubQuery(
|
|
QueryBuilder $queryBuilder,
|
|
string $type,
|
|
?string $attribute,
|
|
$value
|
|
): array {
|
|
|
|
if (!is_array($value)) {
|
|
throw new Error("Bad where item.");
|
|
}
|
|
|
|
$sqQueryBuilder = $this->entityManager
|
|
->getQueryBuilder()
|
|
->select()
|
|
->from($this->entityType);
|
|
|
|
$whereItem = Item::fromRaw([
|
|
'type' => Type::AND,
|
|
'value' => $value,
|
|
]);
|
|
|
|
$whereClause = $this->convert($sqQueryBuilder, $whereItem)->getRaw();
|
|
|
|
$this->scanner->applyLeftJoins($sqQueryBuilder, $whereItem);
|
|
|
|
$rawParams = $sqQueryBuilder->build()->getRaw();
|
|
|
|
$key = $type === 'subQueryIn' ? 'id=s' : 'id!=s';
|
|
|
|
return [
|
|
$key => [
|
|
'select' => ['id'],
|
|
'from' => $this->entityType,
|
|
'whereClause' => $whereClause,
|
|
'leftJoins' => $rawParams['leftJoins'] ?? [],
|
|
'joins' => $rawParams['joins'] ?? [],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function groupProcessColumn(
|
|
QueryBuilder $queryBuilder,
|
|
string $type,
|
|
string $attribute,
|
|
$value
|
|
): array {
|
|
|
|
$link = $this->metadata->get(['entityDefs', $this->entityType, 'fields', $attribute, 'link']);
|
|
$column = $this->metadata->get(['entityDefs', $this->entityType, 'fields', $attribute, 'column']);
|
|
|
|
if (!$column || !$link) {
|
|
throw new Error("Bad where item 'column'.");
|
|
}
|
|
|
|
$alias = $link . 'ColumnFilter' . $this->randomStringGenerator->generate();
|
|
|
|
$queryBuilder->distinct();
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
$columnKey = $alias . 'Middle.' . $column;
|
|
|
|
if ($type === 'columnLike') {
|
|
return [
|
|
$columnKey . '*' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnIn') {
|
|
return [
|
|
$columnKey . '=' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnEquals') {
|
|
return [
|
|
$columnKey . '=' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnNotEquals') {
|
|
return [
|
|
$columnKey . '!=' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnNotIn') {
|
|
return [
|
|
$columnKey . '!=' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnIsNull') {
|
|
return [
|
|
$columnKey . '=' => null,
|
|
];
|
|
}
|
|
|
|
if ($type === 'columnIsNotNull') {
|
|
return [
|
|
$columnKey . '!=' => null,
|
|
];
|
|
}
|
|
|
|
throw new Error("Bad where item 'column'.");
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function groupProcessArray(
|
|
QueryBuilder $queryBuilder,
|
|
string $type,
|
|
string $attribute,
|
|
$value
|
|
): array {
|
|
|
|
$arrayValueAlias = 'arrayFilter' . $this->randomStringGenerator->generate();
|
|
|
|
$arrayAttribute = $attribute;
|
|
$arrayEntityType = $this->entityType;
|
|
$idPart = 'id';
|
|
|
|
$isForeign = strpos($attribute, '.') !== false;
|
|
|
|
$isForeignType = false;
|
|
|
|
$entityDefs = $this->ormDefs->getEntity($this->entityType);
|
|
|
|
if (!$isForeign) {
|
|
$isForeignType = $entityDefs->getAttribute($attribute)->getType() === Entity::FOREIGN;
|
|
|
|
$isForeign = $isForeignType;
|
|
}
|
|
|
|
if ($isForeign) {
|
|
if ($isForeignType) {
|
|
$arrayAttributeLink = $entityDefs->getAttribute($attribute)->getParam('relation');
|
|
$arrayAttribute = $entityDefs->getAttribute($attribute)->getParam('foreign');
|
|
}
|
|
else {
|
|
list($arrayAttributeLink, $arrayAttribute) = explode('.', $attribute);
|
|
}
|
|
|
|
if (!$arrayAttributeLink || !$arrayAttribute) {
|
|
throw new Error("Bad where item.");
|
|
}
|
|
|
|
$arrayEntityType = $entityDefs->getRelation($arrayAttributeLink)->getForeignEntityType();
|
|
|
|
$arrayLinkAlias = $arrayAttributeLink . 'ArrayFilter' . $this->randomStringGenerator->generate();
|
|
|
|
$idPart = $arrayLinkAlias . '.id';
|
|
|
|
$queryBuilder->leftJoin($arrayAttributeLink, $arrayLinkAlias);
|
|
|
|
$relationType = $entityDefs->getRelation($arrayAttributeLink)->getType();
|
|
|
|
if ($relationType === Entity::MANY_MANY || $relationType === Entity::HAS_MANY) {
|
|
$queryBuilder->distinct();
|
|
}
|
|
}
|
|
|
|
if ($type === Type::ARRAY_ANY_OF) {
|
|
if (is_null($value) || !$value && !is_array($value)) {
|
|
throw new Error("Bad where item. No value.");
|
|
}
|
|
|
|
$queryBuilder->leftJoin(
|
|
'ArrayValue',
|
|
$arrayValueAlias,
|
|
[
|
|
$arrayValueAlias . '.entityId:' => $idPart,
|
|
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
|
$arrayValueAlias . '.attribute' => $arrayAttribute,
|
|
]
|
|
);
|
|
|
|
$queryBuilder->distinct();
|
|
|
|
return [
|
|
$arrayValueAlias . '.value' => $value,
|
|
];
|
|
}
|
|
|
|
if ($type === Type::ARRAY_NONE_OF) {
|
|
if (is_null($value) || !$value && !is_array($value)) {
|
|
throw new Error("Bad where item 'array'. No value.");
|
|
}
|
|
|
|
$queryBuilder->leftJoin(
|
|
'ArrayValue',
|
|
$arrayValueAlias,
|
|
[
|
|
$arrayValueAlias . '.entityId:' => $idPart,
|
|
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
|
$arrayValueAlias . '.attribute' => $arrayAttribute,
|
|
$arrayValueAlias . '.value=' => $value,
|
|
]
|
|
);
|
|
|
|
$queryBuilder->distinct();
|
|
|
|
return [
|
|
$arrayValueAlias . '.id' => null,
|
|
];
|
|
}
|
|
|
|
if ($type === Type::ARRAY_IS_EMPTY) {
|
|
$queryBuilder->distinct();
|
|
|
|
$queryBuilder->leftJoin(
|
|
'ArrayValue',
|
|
$arrayValueAlias,
|
|
[
|
|
$arrayValueAlias . '.entityId:' => $idPart,
|
|
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
|
$arrayValueAlias . '.attribute' => $arrayAttribute,
|
|
]
|
|
);
|
|
|
|
return [
|
|
$arrayValueAlias . '.id' => null,
|
|
];
|
|
}
|
|
|
|
if ($type === Type::ARRAY_IS_NOT_EMPTY) {
|
|
$queryBuilder->distinct();
|
|
|
|
$queryBuilder->leftJoin(
|
|
'ArrayValue',
|
|
$arrayValueAlias,
|
|
[
|
|
$arrayValueAlias . '.entityId:' => $idPart,
|
|
$arrayValueAlias . '.entityType' => $arrayEntityType,
|
|
$arrayValueAlias . '.attribute' => $arrayAttribute,
|
|
]
|
|
);
|
|
|
|
return [
|
|
$arrayValueAlias . '.id!=' => null,
|
|
];
|
|
}
|
|
|
|
if ($type === Type::ARRAY_ALL_OF) {
|
|
if (is_null($value) || !$value && !is_array($value)) {
|
|
throw new Error("Bad where item 'array'. No value.");
|
|
}
|
|
|
|
if (!is_array($value)) {
|
|
$value = [$value];
|
|
}
|
|
|
|
$whereList = [];
|
|
|
|
foreach ($value as $arrayValue) {
|
|
$whereList[] = [
|
|
$idPart .'=s' => QueryBuilder::create()
|
|
->from('ArrayValue')
|
|
->select('entityId')
|
|
->where([
|
|
'value' => $arrayValue,
|
|
'attribute' => $arrayAttribute,
|
|
'entityType' => $arrayEntityType,
|
|
'deleted' => false,
|
|
])
|
|
->build()
|
|
->getRaw()
|
|
];
|
|
}
|
|
|
|
return $whereList;
|
|
}
|
|
|
|
throw new Error("Bad where item 'array'.");
|
|
}
|
|
|
|
/**
|
|
* A complex expression w/o a value.
|
|
*
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processExpression(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$key = $attribute;
|
|
|
|
if (substr($key, -1) !== ':') {
|
|
$key .= ':';
|
|
}
|
|
|
|
return [
|
|
$key => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLike(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '*' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNotLike(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '!*' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processEquals(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processOn(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return $this->processEquals($queryBuilder, $attribute, $value);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNotEquals(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '!=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNotOn(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return $this->processNotEquals($queryBuilder, $attribute, $value);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processStartsWith(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '*' => $value . '%',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processEndsWith(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '*' => '%' . $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processContains(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '*' => '%' . $value . '%',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNotContains(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '!*' => '%' . $value . '%',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processGreaterThan(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '>' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processAfter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return $this->processGreaterThan($queryBuilder, $attribute, $value);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLessThan(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '<' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processBefore(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return $this->processLessThan($queryBuilder, $attribute, $value);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processGreaterThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '>=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLessThanOrEquals(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '<=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processIn(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
if (!is_array($value)) {
|
|
throw new Error("Bad where item 'in'.");
|
|
}
|
|
|
|
return [
|
|
$attribute . '=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processNotIn(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
if (!is_array($value)) {
|
|
throw new Error("Bad where item 'notIn'.");
|
|
}
|
|
|
|
return [
|
|
$attribute . '!=' => $value,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processBetween(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
if (!is_array($value) || count($value) < 2) {
|
|
throw new Error("Bad where item 'between'.");
|
|
}
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $value[0],
|
|
$attribute . '<=' => $value[1],
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processAny(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
'true:' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNone(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
'false:' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsNull(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '=' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsNotNull(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '!=' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processEver(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return $this->processIsNotNull($queryBuilder, $attribute, $value);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsTrue(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '=' => true,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsFalse(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '=' => false,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processToday(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '=' => date('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processPast(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '<' => date('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processFuture(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
$attribute . '>' => date('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLastSevenDays(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt1 = new DateTime();
|
|
|
|
$dt2 = clone $dt1;
|
|
|
|
$dt2->modify('-7 days');
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt2->format('Y-m-d'),
|
|
$attribute . '<=' => $dt1->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLastXDays(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt1 = new DateTime();
|
|
|
|
$dt2 = clone $dt1;
|
|
|
|
$number = strval(intval($value));
|
|
|
|
$dt2->modify('-'.$number.' days');
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt2->format('Y-m-d'),
|
|
$attribute . '<=' => $dt1->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNextXDays(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt1 = new DateTime();
|
|
|
|
$dt2 = clone $dt1;
|
|
|
|
$number = strval(intval($value));
|
|
|
|
$dt2->modify('+' . $number . ' days');
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt1->format('Y-m-d'),
|
|
$attribute . '<=' => $dt2->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processOlderThanXDays(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
$number = strval(intval($value));
|
|
|
|
$dt->modify('-' . $number . ' days');
|
|
|
|
return [
|
|
$attribute . '<' => $dt->format('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processAfterXDays(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
$number = strval(intval($value));
|
|
|
|
$dt->modify('+' . $number . ' days');
|
|
|
|
return [
|
|
$attribute . '>' => $dt->format('Y-m-d'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processCurrentMonth(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->modify('first day of this month')->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLastMonth(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->modify('first day of last month')->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processNextMonth(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->modify('first day of next month')->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1M'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws \Exception
|
|
*/
|
|
protected function processCurrentQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
$quarter = ceil($dt->format('m') / 3);
|
|
|
|
$dt->modify('first day of January this year');
|
|
|
|
return [
|
|
'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'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws \Exception
|
|
*/
|
|
protected function processLastQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$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');
|
|
}
|
|
|
|
return [
|
|
'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'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processCurrentYear(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->modify('first day of January this year')->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLastYear(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dt = new DateTime();
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->modify('first day of January last year')->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processCurrentFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dtToday = new DateTime();
|
|
$dt = new DateTime();
|
|
|
|
$fiscalYearShift = $this->config->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');
|
|
}
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processLastFiscalYear(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dtToday = new DateTime();
|
|
$dt = new DateTime();
|
|
|
|
$fiscalYearShift = $this->config->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');
|
|
}
|
|
|
|
$dt->modify('-1 year');
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P1Y'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws \Exception
|
|
*/
|
|
protected function processCurrentFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dtToday = new DateTime();
|
|
$dt = new DateTime();
|
|
|
|
$fiscalYearShift = $this->config->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'));
|
|
}
|
|
}
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P3M'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws \Exception
|
|
*/
|
|
protected function processLastFiscalQuarter(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$dtToday = new DateTime();
|
|
$dt = new DateTime();
|
|
|
|
$fiscalYearShift = $this->config->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'));
|
|
}
|
|
}
|
|
|
|
$dt->modify('-3 months');
|
|
|
|
return [
|
|
'AND' => [
|
|
$attribute . '>=' => $dt->format('Y-m-d'),
|
|
$attribute . '<' => $dt->add(new DateInterval('P3M'))->format('Y-m-d'),
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsNotLinked(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
return [
|
|
'id!=s' => [
|
|
'select' => ['id'],
|
|
'from' => $this->entityType,
|
|
'joins' => [$attribute],
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
*/
|
|
protected function processIsLinked(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$link = $attribute;
|
|
|
|
$alias = $link . 'IsLinkedFilter' . $this->randomStringGenerator->generate();
|
|
|
|
$queryBuilder->distinct();
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
return [
|
|
$alias . '.id!=' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$link = $attribute;
|
|
|
|
if (!$this->ormDefs->getEntity($this->entityType)->hasRelation($link)) {
|
|
throw new Error("Not existing link '{$link}' in where item.");
|
|
}
|
|
|
|
$defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link);
|
|
|
|
$alias = $link . 'LinkedWithFilter' . $this->randomStringGenerator->generate();
|
|
|
|
if (is_null($value) || !$value && !is_array($value)) {
|
|
throw new Error("Bad where item. Empty value.");
|
|
}
|
|
|
|
$relationType = $defs->getType();
|
|
|
|
$queryBuilder->distinct();
|
|
|
|
if ($relationType == Entity::MANY_MANY) {
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
$key = $defs->getForeignMidKey();
|
|
|
|
if (!$key) {
|
|
throw new Error("Bad link '{$link}' in where item.");
|
|
}
|
|
|
|
return [
|
|
$alias . 'Middle.' . $key => $value,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::HAS_MANY) {
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
return [
|
|
$alias . '.id' => $value,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::BELONGS_TO) {
|
|
$key = $defs->getKey();
|
|
|
|
if (!$key) {
|
|
throw new Error("Bad link '{$link}' in where item.");
|
|
}
|
|
|
|
return [
|
|
$key => $value,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::HAS_ONE) {
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
return [
|
|
$alias . '.id' => $value,
|
|
];
|
|
}
|
|
|
|
throw new Error("Bad where item. Not supported relation type.");
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processNotLinkedWith(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$link = $attribute;
|
|
|
|
if (!$this->ormDefs->getEntity($this->entityType)->hasRelation($link)) {
|
|
throw new Error("Not existing link '{$link}' in where item.");
|
|
}
|
|
|
|
$defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link);
|
|
|
|
$alias = $link . 'NotLinkedWithFilter' . $this->randomStringGenerator->generate();
|
|
|
|
if (is_null($value)) {
|
|
throw new Error("Bad where item. Empty value.");
|
|
}
|
|
|
|
$relationType = $defs->getType();
|
|
|
|
$queryBuilder->distinct();
|
|
|
|
if ($relationType == Entity::MANY_MANY) {
|
|
$key = $defs->getForeignMidKey();
|
|
|
|
if (!$key) {
|
|
throw new Error("Bad link '{$link}' in where item.");
|
|
}
|
|
|
|
$queryBuilder->leftJoin(
|
|
$link,
|
|
$alias,
|
|
[$key => $value]
|
|
);
|
|
|
|
return [
|
|
$alias . 'Middle.' . $key => null,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::HAS_MANY) {
|
|
$queryBuilder->leftJoin(
|
|
$link,
|
|
$alias,
|
|
['id' => $value]
|
|
);
|
|
|
|
return [
|
|
$alias . '.id' => null,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::BELONGS_TO) {
|
|
$key = $defs->getKey();
|
|
|
|
if (!$key) {
|
|
throw new Error("Bad link '{$link}' in where item.");
|
|
}
|
|
|
|
return [
|
|
$key . '!=' => $value,
|
|
];
|
|
}
|
|
else if ($relationType == Entity::HAS_ONE) {
|
|
$queryBuilder->leftJoin($link, $alias);
|
|
|
|
return [
|
|
$alias . '.id!=' => $value,
|
|
];
|
|
}
|
|
|
|
throw new Error("Bad where item. Not supported relation type.");
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<mixed,mixed>
|
|
* @throws Error
|
|
*/
|
|
protected function processLinkedWithAll(QueryBuilder $queryBuilder, string $attribute, $value): array
|
|
{
|
|
$link = $attribute;
|
|
|
|
if (!$this->ormDefs->getEntity($this->entityType)->hasRelation($link)) {
|
|
throw new Error("Not existing link '{$link}' in where item.");
|
|
}
|
|
|
|
if (is_null($value) || !$value && !is_array($value)) {
|
|
throw new Error("Bad where item. Empty value.");
|
|
}
|
|
|
|
if (!is_array($value)) {
|
|
$value = [$value];
|
|
}
|
|
|
|
$defs = $this->ormDefs->getEntity($this->entityType)->getRelation($link);
|
|
|
|
$relationType = $defs->getType();
|
|
|
|
if ($relationType === Entity::MANY_MANY) {
|
|
$key = $defs->getForeignMidKey();
|
|
|
|
$whereList = [];
|
|
|
|
foreach ($value as $targetId) {
|
|
$sq = QueryBuilder::create()
|
|
->from($this->entityType)
|
|
->select('id')
|
|
->leftJoin($link)
|
|
->where([
|
|
$link . 'Middle.' . $key => $targetId,
|
|
])
|
|
->build();
|
|
|
|
$whereList[] = ['id=s' => $sq->getRaw()];
|
|
}
|
|
|
|
return $whereList;
|
|
}
|
|
|
|
if ($relationType === Entity::HAS_MANY) {
|
|
$whereList = [];
|
|
|
|
foreach ($value as $targetId) {
|
|
$sq = QueryBuilder::create()
|
|
->from($this->entityType)
|
|
->select('id')
|
|
->leftJoin($link)
|
|
->where([
|
|
$link . '.id' => $targetId,
|
|
])
|
|
->build();
|
|
|
|
$whereList[] = ['id=s' => $sq->getRaw()];
|
|
}
|
|
|
|
return $whereList;
|
|
}
|
|
|
|
throw new Error("Bad where item. Not supported relation type.");
|
|
}
|
|
}
|