From 05de941e6d924f430f053393101fecfb98bd6b27 Mon Sep 17 00:00:00 2001 From: lukmzig Date: Wed, 15 Jan 2025 09:48:15 +0100 Subject: [PATCH 1/2] feat: add simple search endpoint --- config/search.yaml | 33 +++++++ doc/05_Additional_Custom_Attributes.md | 3 +- src/DataIndex/SearchIndexFilter.php | 1 - src/Exception/Api/SearchException.php | 9 +- src/Icon/Service/IconService.php | 14 +++ src/Icon/Service/IconServiceInterface.php | 3 + src/OpenApi/Config/Tags.php | 5 ++ src/Search/Controller/SimpleController.php | 89 +++++++++++++++++++ .../PreResponse/SimpleSearchResultEvent.php | 46 ++++++++++ src/Search/Hydrator/SimpleSearchHydrator.php | 46 ++++++++++ .../SimpleSearchHydratorInterface.php | 28 ++++++ .../MappedParameter/SimpleSearchParameter.php | 40 +++++++++ src/Search/Repository/SearchRepository.php | 69 ++++++++++++++ .../Repository/SearchRepositoryInterface.php | 32 +++++++ src/Search/Schema/SimpleSearchResult.php | 77 ++++++++++++++++ src/Search/Service/SearchService.php | 72 +++++++++++++++ src/Search/Service/SearchServiceInterface.php | 34 +++++++ translations/studio_api_docs.en.yaml | 7 ++ 18 files changed, 604 insertions(+), 4 deletions(-) create mode 100644 config/search.yaml create mode 100644 src/Search/Controller/SimpleController.php create mode 100644 src/Search/Event/PreResponse/SimpleSearchResultEvent.php create mode 100644 src/Search/Hydrator/SimpleSearchHydrator.php create mode 100644 src/Search/Hydrator/SimpleSearchHydratorInterface.php create mode 100644 src/Search/MappedParameter/SimpleSearchParameter.php create mode 100644 src/Search/Repository/SearchRepository.php create mode 100644 src/Search/Repository/SearchRepositoryInterface.php create mode 100644 src/Search/Schema/SimpleSearchResult.php create mode 100644 src/Search/Service/SearchService.php create mode 100644 src/Search/Service/SearchServiceInterface.php diff --git a/config/search.yaml b/config/search.yaml new file mode 100644 index 000000000..0ce95f700 --- /dev/null +++ b/config/search.yaml @@ -0,0 +1,33 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + # controllers are imported separately to make sure they're public + # and have a tag that allows actions to type-hint services + Pimcore\Bundle\StudioBackendBundle\Search\Controller\: + resource: '../src/Search/Controller' + public: true + tags: [ 'controller.service_arguments' ] + + # + # Hydrator + # + + Pimcore\Bundle\StudioBackendBundle\Search\Hydrator\SimpleSearchHydratorInterface: + class: Pimcore\Bundle\StudioBackendBundle\Search\Hydrator\SimpleSearchHydrator + + # + # Repositories + # + + Pimcore\Bundle\StudioBackendBundle\Search\Repository\SearchRepositoryInterface: + class: Pimcore\Bundle\StudioBackendBundle\Search\Repository\SearchRepository + + # + # Services + # + + Pimcore\Bundle\StudioBackendBundle\Search\Service\SearchServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\Search\Service\SearchService diff --git a/doc/05_Additional_Custom_Attributes.md b/doc/05_Additional_Custom_Attributes.md index 518c08d24..c66c60950 100644 --- a/doc/05_Additional_Custom_Attributes.md +++ b/doc/05_Additional_Custom_Attributes.md @@ -137,4 +137,5 @@ final class AssetEvent extends AbstractPreResponseEvent - `pre_response.custom_report_chart_data` - `pre_response.custom_report_report` - `pre_response.custom_report_tree_config_node` -- `pre_response.custom_report_tree_node` \ No newline at end of file +- `pre_response.custom_report_tree_node` +- `pre_response.simple_search.result` \ No newline at end of file diff --git a/src/DataIndex/SearchIndexFilter.php b/src/DataIndex/SearchIndexFilter.php index f89cdd6af..4e1c3177c 100644 --- a/src/DataIndex/SearchIndexFilter.php +++ b/src/DataIndex/SearchIndexFilter.php @@ -50,7 +50,6 @@ public function applyFilters(mixed $parameters, string $type): QueryInterface } // apply type specific filters - foreach ($this->getTypeFilters($filters, $type) as $filter) { $query = $filter->apply($parameters, $query); } diff --git a/src/Exception/Api/SearchException.php b/src/Exception/Api/SearchException.php index f7271ae9c..85836ac23 100644 --- a/src/Exception/Api/SearchException.php +++ b/src/Exception/Api/SearchException.php @@ -17,14 +17,19 @@ namespace Pimcore\Bundle\StudioBackendBundle\Exception\Api; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes; +use Throwable; /** * @internal */ final class SearchException extends AbstractApiException { - public function __construct(string $type) + public function __construct(string $type, ?Throwable $previous = null) { - parent::__construct(HttpResponseCodes::BAD_REQUEST->value, 'Search for ' . $type . ' failed'); + parent::__construct( + HttpResponseCodes::BAD_REQUEST->value, + sprintf('Search for %s failed', $type), + previous: $previous + ); } } diff --git a/src/Icon/Service/IconService.php b/src/Icon/Service/IconService.php index 8a5aff98a..c60d572bc 100644 --- a/src/Icon/Service/IconService.php +++ b/src/Icon/Service/IconService.php @@ -16,7 +16,9 @@ namespace Pimcore\Bundle\StudioBackendBundle\Icon\Service; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Asset\SearchResult\AssetSearchResultItem; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\DataObject\SearchResult\DataObjectSearchResultItem; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\ElementSearchResultItemInterface; use Pimcore\Bundle\StudioBackendBundle\Response\ElementIcon; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementIconTypes; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementTypes; @@ -25,6 +27,18 @@ final class IconService implements IconServiceInterface { private string $defaultIcon = 'unknown'; + public function getIconForElement(ElementSearchResultItemInterface $resultItem): ElementIcon + { + return match (true) { + $resultItem instanceof AssetSearchResultItem => $this->getIconForAsset( + $resultItem->getType(), + $resultItem->getMimeType() + ), + $resultItem instanceof DataObjectSearchResultItem => $this->getIconForDataObject($resultItem), + default => new ElementIcon(ElementIconTypes::NAME->value, $this->defaultIcon) + }; + } + public function getIconForAsset(string $assetType, ?string $mimeType): ElementIcon { if ($assetType === 'document' && $mimeType !== null) { diff --git a/src/Icon/Service/IconServiceInterface.php b/src/Icon/Service/IconServiceInterface.php index 9b348ad76..c566fc25e 100644 --- a/src/Icon/Service/IconServiceInterface.php +++ b/src/Icon/Service/IconServiceInterface.php @@ -17,10 +17,13 @@ namespace Pimcore\Bundle\StudioBackendBundle\Icon\Service; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\DataObject\SearchResult\DataObjectSearchResultItem; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\ElementSearchResultItemInterface; use Pimcore\Bundle\StudioBackendBundle\Response\ElementIcon; interface IconServiceInterface { + public function getIconForElement(ElementSearchResultItemInterface $resultItem): ElementIcon; + public function getIconForAsset(string $assetType, string $mimeType): ElementIcon; public function getIconForDataObject(DataObjectSearchResultItem $dataObject): ElementIcon; diff --git a/src/OpenApi/Config/Tags.php b/src/OpenApi/Config/Tags.php index fe585aaf3..b61cb864e 100644 --- a/src/OpenApi/Config/Tags.php +++ b/src/OpenApi/Config/Tags.php @@ -101,6 +101,10 @@ name: Tags::Schedule->value, description: 'tag_schedule_description' )] +#[Tag( + name: Tags::Search->value, + description: 'tag_search_description' +)] #[Tag( name: Tags::Settings->value, description: 'tag_settings_description' @@ -154,6 +158,7 @@ enum Tags: string case Notifications = 'Notifications'; case Properties = 'Properties'; case Role = 'Role Management'; + case Search = 'Search'; case Schedule = 'Schedule'; case Settings = 'Settings'; case Tags = 'Tags'; diff --git a/src/Search/Controller/SimpleController.php b/src/Search/Controller/SimpleController.php new file mode 100644 index 000000000..ecb5f03db --- /dev/null +++ b/src/Search/Controller/SimpleController.php @@ -0,0 +1,89 @@ +name] + )] + #[PageParameter] + #[PageSizeParameter] + #[TextFieldParameter(name: 'searchTerm', description: 'simple_search_get_search_term_parameter', required: false)] + #[SuccessResponse( + description: 'simple_search_get_success_response', + content: new CollectionJson(new GenericCollection(SimpleSearchResult::class)) + )] + #[DefaultResponses([ + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::BAD_REQUEST, + ])] + public function doSimpleSearch(#[MapQueryString] SimpleSearchParameter $parameters): JsonResponse + { + $collection = $this->searchService->doSimpleSearch($parameters); + + return $this->getPaginatedCollection( + $this->serializer, + $collection->getItems(), + $collection->getTotalItems() + ); + } +} diff --git a/src/Search/Event/PreResponse/SimpleSearchResultEvent.php b/src/Search/Event/PreResponse/SimpleSearchResultEvent.php new file mode 100644 index 000000000..001a71cf9 --- /dev/null +++ b/src/Search/Event/PreResponse/SimpleSearchResultEvent.php @@ -0,0 +1,46 @@ +result); + } + + public function getSimpleSearchResult(): SimpleSearchResult + { + return $this->result; + } + + public function getCustomAttributes(): ?CustomAttributes + { + return $this->result->getCustomAttributes(); + } + + public function setCustomAttributes(CustomAttributes $customAttributes): void + { + $this->result->setCustomAttributes($customAttributes); + } +} diff --git a/src/Search/Hydrator/SimpleSearchHydrator.php b/src/Search/Hydrator/SimpleSearchHydrator.php new file mode 100644 index 000000000..022557df0 --- /dev/null +++ b/src/Search/Hydrator/SimpleSearchHydrator.php @@ -0,0 +1,46 @@ +getId(), + $resultItem->getElementType()->value, + $resultItem->getType(), + $resultItem->getFullPath(), + $this->iconService->getIconForElement($resultItem) + ); + } +} diff --git a/src/Search/Hydrator/SimpleSearchHydratorInterface.php b/src/Search/Hydrator/SimpleSearchHydratorInterface.php new file mode 100644 index 000000000..e93273bd4 --- /dev/null +++ b/src/Search/Hydrator/SimpleSearchHydratorInterface.php @@ -0,0 +1,28 @@ +searchTerm; + } + +} \ No newline at end of file diff --git a/src/Search/Repository/SearchRepository.php b/src/Search/Repository/SearchRepository.php new file mode 100644 index 000000000..3517cad08 --- /dev/null +++ b/src/Search/Repository/SearchRepository.php @@ -0,0 +1,69 @@ +searchProvider->createElementSearch(); + /** @var User $user */ + $user = $this->securityService->getCurrentUser(); + $search->setUser($user); + $search->setPageSize($parameter->getPageSize()); + $search->setPage($parameter->getPage()); + $search->addModifier(new OrderByFullPath(SortDirection::ASC)); + + if ($parameter->getSearchTerm() !== null) { + $search->addModifier(new FullTextSearch($parameter->getSearchTerm())); + } + + try { + return $this->elementSearchService->search($search); + } catch (ElementSearchException $exception) { + throw new SearchException('elements', $exception); + } + } +} \ No newline at end of file diff --git a/src/Search/Repository/SearchRepositoryInterface.php b/src/Search/Repository/SearchRepositoryInterface.php new file mode 100644 index 000000000..78d11d517 --- /dev/null +++ b/src/Search/Repository/SearchRepositoryInterface.php @@ -0,0 +1,32 @@ +id; + } + + public function getElementType(): string + { + return $this->elementType; + } + + public function getType(): string + { + return $this->type; + } + + public function getPath(): string + { + return $this->path; + } + + public function getIcon(): ElementIcon + { + return $this->icon; + } +} diff --git a/src/Search/Service/SearchService.php b/src/Search/Service/SearchService.php new file mode 100644 index 000000000..8ffc6cf1a --- /dev/null +++ b/src/Search/Service/SearchService.php @@ -0,0 +1,72 @@ +searchRepository->searchElements($parameters); + $items = $result->getItems(); + + $hydratedItems = []; + foreach ($items as $item) { + $hydratedItem = $this->simpleSearchHydrator->hydrate($item); + $this->dispatchSearchEvent($hydratedItem); + + $hydratedItems[] = $hydratedItem; + } + + return new Collection($result->getPagination()->getTotalItems(), $hydratedItems); + } + + private function dispatchSearchEvent(SimpleSearchResult $resultItem): void + { + $this->eventDispatcher->dispatch( + new SimpleSearchResultEvent($resultItem), + SimpleSearchResultEvent::EVENT_NAME + ); + } +} diff --git a/src/Search/Service/SearchServiceInterface.php b/src/Search/Service/SearchServiceInterface.php new file mode 100644 index 000000000..3d0ec0ae0 --- /dev/null +++ b/src/Search/Service/SearchServiceInterface.php @@ -0,0 +1,34 @@ +UpdateSchedule schedule_update_for_element_by_type_and_id_success_response: List of updated schedules schedule_update_for_element_by_type_and_id_summary: Update schedules for an element +simple_search_get_description: | + Search for elements based on the given {search term}.
+ Elements are searched with the fulltext filter applied via Generic Data Index.
+simple_search_get_search_term_parameter: Search term +simple_search_get_success_response: Search results for elements +simple_search_get_summary: Search for elements system_settings_get_description: | Get system settings from different providers.
System settings are public and need no login. @@ -485,6 +491,7 @@ tag_notes_description: Note operations to list/delete notes tag_notifications_description: Notification operations to get/delete/send notifications tag_properties_description: Property operations to get/update/create/delete properties tag_role_description: Role Management operations +tag_search_description: Operations to get search configuration, search elements and get search result previews tag_schedule_description: Get schedules for an element tag_settings_description: Get System Settings tag_tags_description: Tag operations to get/list/create/update/delete tags From b47c4fb68d8d54ffd84a95a818011feb9212abcf Mon Sep 17 00:00:00 2001 From: lukmzig <30526586+lukmzig@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:50:48 +0000 Subject: [PATCH 2/2] Apply php-cs-fixer changes --- src/Exception/Api/SearchException.php | 1 + src/Search/MappedParameter/SimpleSearchParameter.php | 4 +--- src/Search/Repository/SearchRepository.php | 6 ++---- src/Search/Repository/SearchRepositoryInterface.php | 1 + src/Search/Schema/SimpleSearchResult.php | 2 +- src/Search/Service/SearchServiceInterface.php | 1 - 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Exception/Api/SearchException.php b/src/Exception/Api/SearchException.php index 85836ac23..02e65c8e2 100644 --- a/src/Exception/Api/SearchException.php +++ b/src/Exception/Api/SearchException.php @@ -18,6 +18,7 @@ use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes; use Throwable; +use function sprintf; /** * @internal diff --git a/src/Search/MappedParameter/SimpleSearchParameter.php b/src/Search/MappedParameter/SimpleSearchParameter.php index 3c390a807..8ba9b21b9 100644 --- a/src/Search/MappedParameter/SimpleSearchParameter.php +++ b/src/Search/MappedParameter/SimpleSearchParameter.php @@ -14,7 +14,6 @@ * @license http://www.pimcore.org/license GPLv3 and PCL */ - namespace Pimcore\Bundle\StudioBackendBundle\Search\MappedParameter; use Pimcore\Bundle\StudioBackendBundle\MappedParameter\CollectionParameters; @@ -36,5 +35,4 @@ public function getSearchTerm(): ?string { return $this->searchTerm; } - -} \ No newline at end of file +} diff --git a/src/Search/Repository/SearchRepository.php b/src/Search/Repository/SearchRepository.php index 3517cad08..39a9cc1d3 100644 --- a/src/Search/Repository/SearchRepository.php +++ b/src/Search/Repository/SearchRepository.php @@ -14,7 +14,6 @@ * @license http://www.pimcore.org/license GPLv3 and PCL */ - namespace Pimcore\Bundle\StudioBackendBundle\Search\Repository; use Pimcore\Bundle\GenericDataIndexBundle\Enum\Search\SortDirection; @@ -39,8 +38,7 @@ public function __construct( private ElementSearchServiceInterface $elementSearchService, private SearchProviderInterface $searchProvider, private SecurityServiceInterface $securityService - ) - { + ) { } /** @@ -66,4 +64,4 @@ public function searchElements(SimpleSearchParameter $parameter): ElementSearchR throw new SearchException('elements', $exception); } } -} \ No newline at end of file +} diff --git a/src/Search/Repository/SearchRepositoryInterface.php b/src/Search/Repository/SearchRepositoryInterface.php index 78d11d517..fa917eeea 100644 --- a/src/Search/Repository/SearchRepositoryInterface.php +++ b/src/Search/Repository/SearchRepositoryInterface.php @@ -20,6 +20,7 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\SearchException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException; use Pimcore\Bundle\StudioBackendBundle\Search\MappedParameter\SimpleSearchParameter; + /** * @internal */ diff --git a/src/Search/Schema/SimpleSearchResult.php b/src/Search/Schema/SimpleSearchResult.php index 713e2e004..2828870d8 100644 --- a/src/Search/Schema/SimpleSearchResult.php +++ b/src/Search/Schema/SimpleSearchResult.php @@ -45,7 +45,7 @@ public function __construct( private readonly string $path, #[Property(description: 'icon', type: ElementIcon::class)] private readonly ElementIcon $icon, - + ) { } diff --git a/src/Search/Service/SearchServiceInterface.php b/src/Search/Service/SearchServiceInterface.php index 3d0ec0ae0..9bfaeb822 100644 --- a/src/Search/Service/SearchServiceInterface.php +++ b/src/Search/Service/SearchServiceInterface.php @@ -16,7 +16,6 @@ namespace Pimcore\Bundle\StudioBackendBundle\Search\Service; -use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\SearchException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException; use Pimcore\Bundle\StudioBackendBundle\Response\Collection;