diff --git a/config/data_index_filters.yaml b/config/data_index_filters.yaml index b338772d9..065e9295a 100644 --- a/config/data_index_filters.yaml +++ b/config/data_index_filters.yaml @@ -34,4 +34,29 @@ services: # DataObject Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\DataObject\ClassNameFilter: - tags: [ 'pimcore.studio_backend.open_search.data_object.filter' ] \ No newline at end of file + tags: [ 'pimcore.studio_backend.open_search.data_object.filter' ] + + # Asset MetaData + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\SelectFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\InputFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\TextAreaFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\CheckboxFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\DateFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\AssetFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\DocumentFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] + + Pimcore\Bundle\StudioBackendBundle\DataIndex\Filter\Asset\MetaData\ObjectFilter: + tags: [ 'pimcore.studio_backend.open_search.asset.filter' ] \ No newline at end of file diff --git a/doc/03_Grid.md b/doc/03_Grid.md index 5f84d6f8a..4144f3bf4 100644 --- a/doc/03_Grid.md +++ b/doc/03_Grid.md @@ -20,3 +20,53 @@ Here you can define `page`, `pageSize` and `includeDescendants`. `page` is the page number of the data you want to get. `pageSize` is the number of items you want to get. `includeDescendants` is a boolean value to include the descendants of the current item. + +### ColumnFilter +It is also possible to filter the data by a column. This is done by adding a `columnFilter` to the `filter` property. +A `columnFilter` has a reference to the column and the value you want to filter by. + +Available filters are: + +| Type | filterValue | Options | +|:-----------------:|:-------------------:|:---------------------:| +| metadata.select | string | | +| metadata.date | object of timestamp | `from`, `to`, or `on` | +| metadata.input | string | | +| metadata.checkbox | boolean | | +| metadata.textarea | string | | +| metadata.object | integer | ID of the object | +| metadata.document | integer | ID fo the document | +| metadata.asset | integer | ID fo the asset | + + + +### Examples: + +Filter by a select column: +```json +... +"columnFilters" [ + { + "key": "selectKey", + "type": "metadata.select", + "filterValue": "selectValue" + } +] +... +``` + +Filter by a date column: +```json +... +"columnFilters" [ + { + "key": "selectKey", + "type": "metadata.select", + "filterValue": { + "from": 1719792000, + "to": 1718792000 + } + } +] +... +``` diff --git a/src/DataIndex/Filter/Asset/MetaData/AssetFilter.php b/src/DataIndex/Filter/Asset/MetaData/AssetFilter.php new file mode 100644 index 000000000..e74175d65 --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/AssetFilter.php @@ -0,0 +1,60 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_ASSET->value) as $column) { + $assetQuery = $this->applyAssetFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyAssetFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_int($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for asset must be a integer (ID of the asset)'); + } + + $query->filterMetaData($column->getKey(), FilterType::ASSET->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/CheckboxFilter.php b/src/DataIndex/Filter/Asset/MetaData/CheckboxFilter.php new file mode 100644 index 000000000..23fdd234c --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/CheckboxFilter.php @@ -0,0 +1,60 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_CHECKBOX->value) as $column) { + $assetQuery = $this->applyCheckboxFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyCheckboxFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_bool($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for checkbox must be a boolean'); + } + + $query->filterMetaData($column->getKey(), FilterType::CHECKBOX->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/DateFilter.php b/src/DataIndex/Filter/Asset/MetaData/DateFilter.php new file mode 100644 index 000000000..4dadab91e --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/DateFilter.php @@ -0,0 +1,85 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_DATE->value) as $column) { + $assetQuery = $this->applyDateFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyDateFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_array($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for date must be an array'); + } + + $filterValue = $column->getFilterValue(); + + if (isset($filterValue['on'])) { + $query->filterMetaData( + $column->getKey(), + FilterType::DATE->value, + [GenericDateFilter::PARAM_ON => $filterValue['on']] + ); + } + + if (isset($filterValue['to'])) { + $query->filterMetaData( + $column->getKey(), + FilterType::DATE->value, + [GenericDateFilter::PARAM_END => $filterValue['to']] + ); + } + + if (isset($filterValue['from'])) { + $query->filterMetaData( + $column->getKey(), + FilterType::DATE->value, + [GenericDateFilter::PARAM_START => $filterValue['from']] + ); + } + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/DocumentFilter.php b/src/DataIndex/Filter/Asset/MetaData/DocumentFilter.php new file mode 100644 index 000000000..d04e2803b --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/DocumentFilter.php @@ -0,0 +1,60 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_DOCUMENT->value) as $column) { + $assetQuery = $this->applyDocumentFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyDocumentFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_int($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for document must be a integer (ID of the document)'); + } + + $query->filterMetaData($column->getKey(), FilterType::DOCUMENT->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/FilterType.php b/src/DataIndex/Filter/Asset/MetaData/FilterType.php new file mode 100644 index 000000000..6b6404ff7 --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/FilterType.php @@ -0,0 +1,30 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_INPUT->value) as $column) { + $assetQuery = $this->applyInputFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyInputFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_string($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for input must be a string'); + } + + $query->filterMetaData($column->getKey(), FilterType::INPUT->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/IsAssetMetaDataTrait.php b/src/DataIndex/Filter/Asset/MetaData/IsAssetMetaDataTrait.php new file mode 100644 index 000000000..ae601f879 --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/IsAssetMetaDataTrait.php @@ -0,0 +1,44 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_DATA_OBJECT->value) as $column) { + $assetQuery = $this->applyAssetFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyAssetFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_int($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for object must be a integer (ID of the object)'); + } + + $query->filterMetaData($column->getKey(), FilterType::OBJECT->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/SelectFilter.php b/src/DataIndex/Filter/Asset/MetaData/SelectFilter.php new file mode 100644 index 000000000..7f42e6c5b --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/SelectFilter.php @@ -0,0 +1,60 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_SELECT->value) as $column) { + $assetQuery = $this->applySelectFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applySelectFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_string($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for select must be a string'); + } + + $query->filterMetaData($column->getKey(), FilterType::SELECT->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Filter/Asset/MetaData/TextAreaFilter.php b/src/DataIndex/Filter/Asset/MetaData/TextAreaFilter.php new file mode 100644 index 000000000..f31f4b203 --- /dev/null +++ b/src/DataIndex/Filter/Asset/MetaData/TextAreaFilter.php @@ -0,0 +1,60 @@ +validateParameterType($parameters); + $assetQuery = $this->validateQueryType($query); + + if (!$parameters || !$assetQuery) { + return $query; + } + + foreach ($parameters->getColumnFilterByType(ColumnType::METADATA_TEXTAREA->value) as $column) { + $assetQuery = $this->applyTextAreaFilter($column, $assetQuery); + } + + return $assetQuery; + } + + private function applyTextAreaFilter(ColumnFilter $column, AssetQuery $query): AssetQuery + { + if (!is_string($column->getFilterValue())) { + throw new InvalidArgumentException('Filter value for textarea must be a string'); + } + + $query->filterMetaData($column->getKey(), FilterType::TEXTAREA->value, $column->getFilterValue()); + + return $query; + } +} diff --git a/src/DataIndex/Query/AssetQuery.php b/src/DataIndex/Query/AssetQuery.php index 6ff6295d3..a354f576a 100644 --- a/src/DataIndex/Query/AssetQuery.php +++ b/src/DataIndex/Query/AssetQuery.php @@ -18,6 +18,7 @@ use Pimcore\Bundle\GenericDataIndexBundle\Enum\Search\SortDirection; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Asset\AssetMetaDataFilter; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\ExcludeFoldersFilter; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Basic\IdsFilter; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Tree\ParentIdFilter; @@ -97,4 +98,11 @@ public function searchByIds(array $ids): self return $this; } + + public function filterMetaData(string $name, string $type, mixed $data): self + { + $this->search->addModifier(new AssetMetaDataFilter($name, $type, $data)); + + return $this; + } } diff --git a/src/Grid/MappedParameter/FilterParameter.php b/src/Grid/MappedParameter/FilterParameter.php index 4ac78373c..f1d551b34 100644 --- a/src/Grid/MappedParameter/FilterParameter.php +++ b/src/Grid/MappedParameter/FilterParameter.php @@ -16,7 +16,10 @@ namespace Pimcore\Bundle\StudioBackendBundle\Grid\MappedParameter; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\MappedParameter\CollectionParametersInterface; +use Pimcore\Bundle\StudioBackendBundle\MappedParameter\Filter\ColumnFilter; +use Pimcore\Bundle\StudioBackendBundle\MappedParameter\Filter\ColumnFiltersParameterInterface; use Pimcore\Bundle\StudioBackendBundle\MappedParameter\Filter\ExcludeFolderParameterInterface; use Pimcore\Bundle\StudioBackendBundle\MappedParameter\Filter\PathParameterInterface; @@ -26,14 +29,16 @@ final class FilterParameter implements CollectionParametersInterface, ExcludeFolderParameterInterface, - PathParameterInterface + PathParameterInterface, + ColumnFiltersParameterInterface { private ?string $path = null; public function __construct( private readonly int $page = 1, private readonly int $pageSize = 50, - private readonly bool $includeDescendants = true + private readonly bool $includeDescendants = true, + private readonly array $columnFilters = [] ) { } @@ -71,4 +76,24 @@ public function getPathIncludeDescendants(): bool { return $this->includeDescendants; } + + /** + * @return ColumnFilter[] + */ + public function getColumnFilterByType(string $type): iterable + { + $columns = array_filter($this->columnFilters, fn ($columnFilter) => $columnFilter['type'] === $type); + + foreach ($columns as $column) { + if (!isset($column['key'], $column['type'], $column['filterValue'])) { + throw new InvalidArgumentException('Invalid column filter'); + } + + yield new ColumnFilter( + $column['key'], + $column['type'], + $column['filterValue'] + ); + } + } } diff --git a/src/Grid/Schema/Filter.php b/src/Grid/Schema/Filter.php index 7f0eb2c8a..13e40b994 100644 --- a/src/Grid/Schema/Filter.php +++ b/src/Grid/Schema/Filter.php @@ -38,6 +38,13 @@ public function __construct( private int $pageSize, #[Property(description: 'Include Descendant Items', type: 'boolean', example: false)] private string $includeDescendants, + #[Property( + description: 'Column Filter', + type: 'object', + example: '[{"key":"name","type": "metadata.object","value": 1}]' + )] + private array $columnFilters = [], + ) { } @@ -55,4 +62,9 @@ public function getIncludeDescendants(): string { return $this->includeDescendants; } + + public function getColumnFilters(): array + { + return $this->columnFilters; + } } diff --git a/src/MappedParameter/Filter/ColumnFilter.php b/src/MappedParameter/Filter/ColumnFilter.php new file mode 100644 index 000000000..ecd9ef2f9 --- /dev/null +++ b/src/MappedParameter/Filter/ColumnFilter.php @@ -0,0 +1,45 @@ +key; + } + + public function getType(): string + { + return $this->type; + } + + public function getFilterValue(): mixed + { + return $this->filterValue; + } +} diff --git a/src/MappedParameter/Filter/ColumnFiltersParameterInterface.php b/src/MappedParameter/Filter/ColumnFiltersParameterInterface.php new file mode 100644 index 000000000..b980176a1 --- /dev/null +++ b/src/MappedParameter/Filter/ColumnFiltersParameterInterface.php @@ -0,0 +1,28 @@ +