diff --git a/config/notes.yaml b/config/notes.yaml index 1d01bde4c..4164d7e15 100644 --- a/config/notes.yaml +++ b/config/notes.yaml @@ -23,3 +23,6 @@ services: Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteService + + Pimcore\Bundle\StudioBackendBundle\Note\Service\FilterServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\Note\Service\FilterService diff --git a/src/Exception/InvalidFilterException.php b/src/Exception/InvalidFilterException.php new file mode 100644 index 000000000..f2d8d71d0 --- /dev/null +++ b/src/Exception/InvalidFilterException.php @@ -0,0 +1,31 @@ +setOrder($parameters->getSortOrder()); } - if ($parameters->getFilter()) { - $condition = '(' - . '`title` LIKE :filter' - . ' OR `description` LIKE :filter' - . ' OR `type` LIKE :filter' - . ' OR `user` IN (SELECT `id` FROM `users` WHERE `name` LIKE :filter)' - . " OR DATE_FORMAT(FROM_UNIXTIME(`date`), '%Y-%m-%d') LIKE :filter" - . ')'; - $list->addConditionParam($condition, ['filter' => '%' . $parameters->getFilter() . '%']); - } + $this->filterService->applyFilter($list, $parameters); - if ($noteElement->getId() && $noteElement->getType()) { - $list->addConditionParam( - '(cid = :id AND ctype = :type)', - ['id' => $noteElement->getId(), 'type' => $noteElement->getType()] - ); - } + $this->filterService->applyFieldFilters($list, $parameters); + + $this->filterService->applyElementFilter($list, $noteElement); return $list; } diff --git a/src/Note/Request/NoteParameters.php b/src/Note/Request/NoteParameters.php index 9e493d195..865e0a19c 100644 --- a/src/Note/Request/NoteParameters.php +++ b/src/Note/Request/NoteParameters.php @@ -16,6 +16,7 @@ namespace Pimcore\Bundle\StudioBackendBundle\Note\Request; +use JsonException; use Pimcore\Bundle\StudioBackendBundle\Request\CollectionParameters; /** @@ -24,11 +25,12 @@ final readonly class NoteParameters extends CollectionParameters { public function __construct( + int $page = 1, + int $pageSize = 50, private ?string $sortBy = null, private ?string $sortOrder = null, private ?string $filter = null, - int $page = 1, - int $pageSize = 50, + private ?string $fieldFilters = null, ) { parent::__construct($page, $pageSize); } @@ -47,4 +49,18 @@ public function getFilter(): ?string { return $this->filter; } + + /** + * @throws JsonException + */ + public function getFieldFilters(): ?array + { + return $this->fieldFilters === null ? null : + json_decode( + $this->fieldFilters, + true, + 512, + JSON_THROW_ON_ERROR + ); + } } diff --git a/src/Note/Service/FilterService.php b/src/Note/Service/FilterService.php new file mode 100644 index 000000000..6a6366a92 --- /dev/null +++ b/src/Note/Service/FilterService.php @@ -0,0 +1,139 @@ +getFilter()) { + $list->addConditionParam( + $this->createFilterCondition($parameters->getFilter()), + ['filter' => '%' . $parameters->getFilter() . '%'] + ); + } + } + + public function applyFieldFilters(NoteListing $list, NoteParameters $parameters): void + { + try { + if(!$parameters->getFieldFilters() || empty($parameters->getFieldFilters())) { + return; + } + + $propertyKey = 'field'; + + foreach ($parameters->getFieldFilters() as $filter) { + $operator = $this->findOperator($filter['type'], $filter['operator']); + $value = $this->prepareValue($filter['type'], $filter['operator'], $filter['value']); + + if ($operator === 'LIKE') { + $value = '%' . $value . '%'; + } + + if ($filter[$propertyKey] === 'user') { + $list->addConditionParam( + '`user` IN (SELECT `id` FROM `users` WHERE `name` ' . $operator . ' :user)', + ['user' => $value] + ); + } + + if ($filter['type'] === 'date' && $filter['operator'] === 'eq') { + $maxTime = $value + (86400 - 1); //specifies the top point of the range used in the condition + $dateCondition = '`' . $filter[$propertyKey] . '` ' . ' BETWEEN :minTime AND :maxTime'; + $list->addConditionParam($dateCondition, ['minTime' => $value, 'maxTime' => $maxTime]); + } else { + $list->addConditionParam( + '`' . $filter[$propertyKey] . '` ' . $operator . ' :' . $filter[$propertyKey], + [$filter[$propertyKey] => $value] + ); + } + } + } catch (Exception) { + throw new InvalidFilterException('fieldFilters'); + } + } + + public function applyElementFilter(NoteListing $list, NoteElement $noteElement): void + { + if ($noteElement->getId() && $noteElement->getType()) { + $list->addConditionParam( + '(cid = :id AND ctype = :type)', + ['id' => $noteElement->getId(), 'type' => $noteElement->getType()] + ); + } + } + + private function prepareValue(string $type, string $operator, string $value): mixed + { + return match($type) { + 'date' => strtotime($value), + default => $this->matchValueOperator($operator, $value) + }; + } + + + private function matchValueOperator(string $operator, mixed $value): mixed + { + return match($operator) { + 'boolean' => (int)$value, + default => $value + }; + } + + private function createFilterCondition(string $filter): string + { + return '(' + . '`title` LIKE :filter' + . ' OR `description` LIKE :filter' + . ' OR `type` LIKE :filter' + . ' OR `user` IN (SELECT `id` FROM `users` WHERE `name` LIKE :filter)' + . " OR DATE_FORMAT(FROM_UNIXTIME(`date`), '%Y-%m-%d') LIKE :filter" + . ')'; + } + + + private function findOperator(string $type, string $operator): string + { + return match($type) { + 'string' => 'LIKE', + 'numeric', 'date' => $this->matchNumericOperator($operator), + default => '=' + }; + } + + private function matchNumericOperator(string $operator): string + { + return match($operator) { + 'lt' => '<', + 'lte' => '<=', + 'gt' => '>', + 'gte' => '>=', + default => '=' + }; + } +} diff --git a/src/Note/Service/FilterServiceInterface.php b/src/Note/Service/FilterServiceInterface.php new file mode 100644 index 000000000..081ce15f2 --- /dev/null +++ b/src/Note/Service/FilterServiceInterface.php @@ -0,0 +1,33 @@ +