This commit is contained in:
Yuri Kuznetsov
2022-10-16 17:40:33 +03:00
parent 1c06f3be92
commit 9d5e1b7d33
8 changed files with 201 additions and 192 deletions
+51 -38
View File
@@ -31,12 +31,14 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\{
Api\Request,
Exceptions\Forbidden,
Exceptions\NotFound,
Record\SearchParamsFetcher};
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Field\DateTime;
use Espo\Core\Record\SearchParamsFetcher;
use Espo\Entities\User as UserEntity;
use Espo\Tools\Stream\FindParams;
use Espo\Tools\Stream\Service as Service;
use stdClass;
@@ -63,27 +65,35 @@ class Stream
*/
public function getActionList(Request $request): stdClass
{
$params = $request->getRouteParams();
$id = $request->getRouteParam('id');
$scope = $request->getRouteParam('scope');
$scope = $params['scope'];
$id = isset($params['id']) ? $params['id'] : null;
if ($scope === null) {
throw new BadRequest();
}
if ($id === null && $scope !== UserEntity::ENTITY_TYPE) {
throw new BadRequest("No ID.");
}
$searchParams = $this->searchParamsFetcher->fetch($request);
$offset = $searchParams->getOffset();
$maxSize = $searchParams->getMaxSize();
$after = $request->getQueryParam('after');
$filter = $request->getQueryParam('filter');
$skipOwn = $request->getQueryParam('skipOwn') === 'true';
$result = $this->service->find($scope, $id, [
'offset' => $offset,
'maxSize' => $maxSize,
'after' => $after,
'filter' => $filter,
'skipOwn' => $skipOwn,
]);
/** @todo Use named params. */
$findParams = new FindParams(
$searchParams,
$skipOwn,
$after ?
DateTime::fromString($after) : null,
$filter
);
$result = $scope === UserEntity::ENTITY_TYPE ?
$this->service->findUserStream($id, $findParams) :
$this->service->find($scope, $id ?? '', $findParams);
return (object) [
'total' => $result->getTotal(),
@@ -98,30 +108,33 @@ class Stream
*/
public function getActionListPosts(Request $request): stdClass
{
$params = $request->getRouteParams();
$id = $request->getRouteParam('id');
$scope = $request->getRouteParam('scope');
$scope = $params['scope'];
$id = isset($params['id']) ? $params['id'] : null;
$searchParams = $this->searchParamsFetcher->fetch($request);
$offset = $searchParams->getOffset();
$maxSize = $searchParams->getMaxSize();
$after = $request->getQueryParam('after');
$where = $request->getQueryParams()['where'] ?? null;
if ($where !== null && !is_array($where)) {
if ($scope === null) {
throw new BadRequest();
}
$result = $this->service->find($scope, $id, [
'offset' => $offset,
'maxSize' => $maxSize,
'after' => $after,
'filter' => 'posts',
'where' => $where,
]);
if ($id === null && $scope !== UserEntity::ENTITY_TYPE) {
throw new BadRequest("No ID.");
}
$after = $request->getQueryParam('after');
$searchParams = $this->searchParamsFetcher->fetch($request);
/** @todo Use named params. */
$findParams = new FindParams(
$searchParams,
false,
$after ?
DateTime::fromString($after) : null,
FindParams::FILTER_POSTS
);
$result = $scope === UserEntity::ENTITY_TYPE ?
$this->service->findUserStream($id, $findParams) :
$this->service->find($scope, $id ?? '', $findParams);
return (object) [
'total' => $result->getTotal(),
@@ -45,7 +45,6 @@ class SearchParamsFetcher
private const MAX_SIZE_LIMIT = 200;
private Config $config;
private TextMetadataProvider $textMetadataProvider;
public function __construct(Config $config, TextMetadataProvider $textMetadataProvider)
@@ -37,16 +37,15 @@ use stdClass;
/**
* Search parameters.
*
* @immutable
*/
class SearchParams
{
/**
* @var array<string,mixed>
*/
/** @var array<string,mixed> */
private array $rawParams = [];
public const ORDER_ASC = 'ASC';
public const ORDER_DESC = 'DESC';
private function __construct() {}
@@ -46,9 +46,7 @@ class Applier
private string $entityType;
private User $user;
private ConverterFactory $converterFactory;
private CheckerFactory $checkerFactory;
public function __construct(
+4 -7
View File
@@ -35,21 +35,18 @@ use Espo\Core\Select\Where\Item\Data\DateTime as DateTimeData;
use InvalidArgumentException;
use RuntimeException;
/**
* @immutable
*/
class Item
{
public const TYPE_AND = Item\Type::AND;
public const TYPE_OR = Item\Type::OR;
private string $type;
private ?string $attribute = null;
/**
* @var mixed
*/
/** @var mixed */
private $value = null;
private ?Data $data = null;
/**
@@ -32,118 +32,61 @@ namespace Espo\Core\Select\Where\Item;
class Type
{
public const AND = 'and';
public const OR = 'or';
public const NOT = 'not';
public const SUBQUERY_NOT_IN = 'subQueryNotIn';
public const SUBQUERY_IN = 'subQueryIn';
public const EXPRESSION = 'expression';
public const IN = 'in';
public const NOT_IN = 'notIn';
public const EQUALS = 'equals';
public const NOT_EQUALS = 'notEquals';
public const ON = 'on';
public const NOT_ON = 'notOn';
public const LIKE = 'like';
public const NOT_LIKE = 'notLike';
public const STARTS_WITH = 'startsWith';
public const ENDS_WITH = 'endsWith';
public const CONTAINS = 'contains';
public const GREATER_THAN = 'greaterThan';
public const LESS_THAN = 'lessThan';
public const GREATER_THAN_OR_EQUALS = 'greaterThanOrEquals';
public const LESS_THAN_OR_EQUALS = 'lessThanOrEquals';
public const AFTER = 'after';
public const BEFORE = 'before';
public const BETWEEN = 'between';
public const ANY = 'any';
public const NONE = 'none';
public const IS_NULL = 'isNull';
public const IS_NOT_NULL = 'isNotNull';
public const IS_TRUE = 'isTrue';
public const IS_FALSE = 'isFalse';
public const TODAY = 'today';
public const PAST = 'past';
public const FUTURE = 'future';
public const LAST_X_DAYS = 'lastXDays';
public const NEXT_X_DAYS = 'nextXDays';
public const OLDER_THAN_X_DAYS = 'olderThanXDays';
public const AFTER_X_DAYS = 'afterXDays';
public const CURRENT_MONTH = 'currentMonth';
public const NEXT_MONTH = 'nextMonth';
public const LAST_MONTH = 'lastMonth';
public const CURRENT_QUARTER = 'currentQuarter';
public const LAST_QUARTER = 'lastQuarter';
public const CURRENT_YEAR = 'currentYear';
public const LAST_YEAR = 'lastYear';
public const CURRENT_FISCAL_YEAR = 'currentFiscalYear';
public const LAST_FISCAL_YEAR = 'lastFiscalYear';
public const CURRENT_FISCAL_QUARTER = 'currentFiscalQuarter';
public const LAST_FISCAL_QUARTER = 'lastFiscalQuarter';
public const ARRAY_ANY_OF = 'arrayAnyOf';
public const ARRAY_NONE_OF = 'arrayNoneOf';
public const ARRAY_ALL_OF = 'arrayAllOf';
public const ARRAY_IS_EMPTY = 'arrayIsEmpty';
public const ARRAY_IS_NOT_EMPTY = 'arrayIsNotEmpty';
public const IS_LINKED_WITH = 'linkedWith';
public const IS_NOT_LINKED_WITH = 'notLinkedWith';
public const IS_LINKED_WITH_ALL = 'linkedWithAll';
public const IS_LINKED_WITH_ANY = 'isLinked';
public const IS_LINKED_WITH_NONE = 'isNotLinked';
}
@@ -0,0 +1,90 @@
<?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\Tools\Stream;
use Espo\Core\Field\DateTime;
use Espo\Core\Select\SearchParams;
use Espo\Core\Select\Where\Item as WhereItem;
/**
* @immutable
*/
class FindParams
{
public const FILTER_POSTS = 'posts';
public const FILTER_UPDATES = 'updates';
private SearchParams $searchParams;
private bool $skipOwn;
private ?DateTime $after;
private ?string $filter;
public function __construct(
SearchParams $searchParams,
bool $skipOwn = false,
?DateTime $after = null,
?string $filter = null,
) {
$this->searchParams = $searchParams;
$this->skipOwn = $skipOwn;
$this->after = $after;
$this->filter = $filter;
}
public function getSearchParams(): SearchParams
{
return $this->searchParams;
}
public function getMaxSize(): ?int
{
return $this->searchParams->getMaxSize();
}
public function getOffset(): ?int
{
return $this->searchParams->getOffset();
}
public function skipOwn(): bool
{
return $this->skipOwn;
}
public function getAfter(): ?DateTime
{
return $this->after;
}
public function getFilter(): ?string
{
return $this->filter;
}
}
+53 -83
View File
@@ -388,22 +388,16 @@ class Service
}
/**
* @param array{
* offset?: int|null,
* maxSize: int|null,
* skipOwn?: bool,
* where?: ?array<mixed,mixed>,
* after?: ?string,
* filter?: ?string,
* } $params
* @throws NotFound
* @throws Forbidden
* @return RecordCollection<Note>
*/
public function findUserStream(string $userId, array $params): RecordCollection
public function findUserStream(?string $userId, FindParams $params): RecordCollection
{
$offset = intval($params['offset'] ?? 0);
$maxSize = intval($params['maxSize']);
$userId ??= $this->user->getId();
$offset = $params->getOffset() ?? 0;
$maxSize = $params->getMaxSize();
$sqLimit = $offset + $maxSize + 1;
@@ -412,15 +406,13 @@ class Service
$this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId);
if (!$user) {
throw new NotFound();
throw new NotFound("User not found.");
}
if (!$this->acl->checkUserPermission($user, 'user')) {
throw new Forbidden();
throw new Forbidden("No user permission access.");
}
$skipOwn = $params['skipOwn'] ?? false;
$teamIdList = $user->getTeamIdList();
$select = [
@@ -447,11 +439,14 @@ class Service
$additionalQuery = null;
if (!empty($params['where'])) {
$searchParams = SearchParams::fromRaw([
'where' => $params['where'],
]);
$searchParams = $params->getSearchParams();
if (
$searchParams->getWhere() ||
$searchParams->getTextFilter() ||
$searchParams->getPrimaryFilter() ||
$searchParams->getBoolFilterList() !== []
) {
$additionalQuery = $this->selectBuilderFactory
->create()
->from(Note::ENTITY_TYPE)
@@ -780,7 +775,7 @@ class Service
->build();
}
if ($skipOwn) {
if ($params->skipOwn()) {
foreach ($queryList as $i => $query) {
$queryList[$i] = $this->entityManager
->getQueryBuilder()
@@ -803,7 +798,7 @@ class Service
])
->build();
if (
if (
(!$user->isPortal() || $user->isAdmin()) &&
!$user->isApi()
) {
@@ -892,39 +887,24 @@ class Service
}
/**
* @param array{
* offset?: int|null,
* maxSize: int|null,
* skipOwn?: bool,
* where?: ?array<mixed,mixed>,
* after?: ?string,
* filter?: ?string,
* } $params
* @return array<mixed,mixed>
*/
private function getUserStreamWhereClause(array $params, User $user): array
private function getUserStreamWhereClause(FindParams $params, User $user): array
{
$whereClause = [];
if (!empty($params['after'])) {
$whereClause[]['createdAt>'] = $params['after'];
if ($params->getAfter()) {
$whereClause[]['createdAt>'] = $params->getAfter()->getString();
}
if (!empty($params['filter'])) {
switch ($params['filter']) {
case 'posts':
$whereClause[]['type'] = Note::TYPE_POST;
break;
case 'updates':
$whereClause[]['type'] = [
Note::TYPE_UPDATE,
Note::TYPE_STATUS,
];
break;
}
if ($params->getFilter() === FindParams::FILTER_POSTS) {
$whereClause[]['type'] = Note::TYPE_POST;
}
else if ($params->getFilter() === FindParams::FILTER_UPDATES) {
$whereClause[]['type'] = [
Note::TYPE_UPDATE,
Note::TYPE_STATUS,
];
}
return $whereClause;
@@ -936,29 +916,17 @@ class Service
}
/**
* @param array{
* offset?: int|null,
* maxSize: int|null,
* skipOwn?: bool,
* where?: ?array<mixed,mixed>,
* after?: ?string,
* filter?: ?string,
* } $params
* @throws NotFound
* @throws Forbidden
* @return RecordCollection<Note>
*/
public function find(string $scope, ?string $id, array $params): RecordCollection
public function find(string $scope, string $id, FindParams $params): RecordCollection
{
if ($scope === User::ENTITY_TYPE) {
if (empty($id)) {
$id = $this->user->getId();
}
return $this->findUserStream($id, $params);
throw new Forbidden();
}
$entity = $this->entityManager->getEntity($scope, $id);
$entity = $this->entityManager->getEntityById($scope, $id);
$onlyTeamEntityTypeList = $this->getOnlyTeamEntityTypeList($this->user);
$onlyOwnEntityTypeList = $this->getOnlyOwnEntityTypeList($this->user);
@@ -973,11 +941,14 @@ class Service
$additionalQuery = null;
if (!empty($params['where'])) {
$searchParams = SearchParams::fromRaw([
'where' => $params['where'],
]);
$searchParams = $params->getSearchParams();
if (
$searchParams->getWhere() ||
$searchParams->getTextFilter() ||
$searchParams->getPrimaryFilter() ||
$searchParams->getBoolFilterList() !== []
) {
$additionalQuery = $this->selectBuilderFactory
->create()
->from(Note::ENTITY_TYPE)
@@ -1116,21 +1087,16 @@ class Service
}
}
if (!empty($params['filter'])) {
switch ($params['filter']) {
case 'posts':
$where['type'] = Note::TYPE_POST;
$filter = $params->getFilter();
break;
case 'updates':
$where['type'] = [
Note::TYPE_ASSIGN,
Note::TYPE_STATUS,
];
break;
}
if ($filter === FindParams::FILTER_POSTS) {
$where['type'] = Note::TYPE_POST;
}
else if ($filter === FindParams::FILTER_UPDATES) {
$where['type'] = [
Note::TYPE_ASSIGN,
Note::TYPE_STATUS,
];
}
$ignoreScopeList = $this->getIgnoreScopeList($this->user);
@@ -1162,16 +1128,20 @@ class Service
$builder->where($where);
if (!empty($params['after'])) {
$after = $params->getAfter();
$offset = $params->getOffset();
$maxSize = $params->getMaxSize();
if ($after) {
$builder->where([
'createdAt>' => $params['after'],
'createdAt>' => $after->getString(),
]);
}
$countBuilder = clone $builder;
$builder
->limit($params['offset'] ?? 0, $params['maxSize'])
->limit($offset ?? 0, $maxSize)
->order('number', 'DESC');
$collection = $this->entityManager