diff --git a/composer.json b/composer.json index f1ea303fd..a1854b4a3 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "pimcore/static-resolver-bundle": "^1.3", "pimcore/generic-data-index-bundle": "1.x-dev", "pimcore/pimcore": "^11.0", - "api-platform/core": "^3.2" + "api-platform/core": "^3.2", + "nelmio/api-doc-bundle": "^4.2" }, "require-dev": { "pimcore/admin-ui-classic-bundle": "^v1.3", diff --git a/config/api/V1/translation.yaml b/config/api/V1/translation.yaml new file mode 100644 index 000000000..f35b1d9c2 --- /dev/null +++ b/config/api/V1/translation.yaml @@ -0,0 +1,36 @@ +/api/v1/translations: + post: + tags: [Translation] + summary: Get all translations + requestBody: + description: Locale and array of keys + content: + application/json: + schema: + ref: '#/components/schemas/Translation' + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + locale: + type: string + example: en + keys: + type: array + items: + type: string + example: 'key1' + '403': + description: Unauthorized + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'Unauthorized' \ No newline at end of file diff --git a/config/api_platform/resources/asset.yaml b/config/api_platform/resources/asset.yaml deleted file mode 100644 index d4998a710..000000000 --- a/config/api_platform/resources/asset.yaml +++ /dev/null @@ -1,31 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Asset: - #security: 'is_granted("API_PLATFORM")' - operations: - ApiPlatform\Metadata\GetCollection: - filters: - - Pimcore\Bundle\StudioApiBundle\Filter\AssetParentIdFilter - - Pimcore\Bundle\StudioApiBundle\Filter\AssetIdSearchFilter - - Pimcore\Bundle\StudioApiBundle\Filter\AssetExcludeFolderFilter - - Pimcore\Bundle\StudioApiBundle\Filter\AssetPathFilter - paginationClientItemsPerPage: true - ApiPlatform\Metadata\Get: - normalizationContext: - groups: [ 'asset:read', 'asset:item:get', 'dependency:read', 'property:read', 'element:read', 'element:item:get'] - ApiPlatform\Metadata\Put: - ApiPlatform\Metadata\Post: - ApiPlatform\Metadata\Delete: - ApiPlatform\Metadata\Patch: - provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider - normalizationContext: - groups: ['asset:read', 'dependency:read', 'property:read', 'element:read'] - denormalizationContext: - groups: ['asset:write'] - properties: - id: - identifier: true - -properties: - Pimcore\Bundle\StudioApiBundle\Dto\Asset: - dependencies: - genId: false \ No newline at end of file diff --git a/config/api_platform/resources/asset/document.yaml b/config/api_platform/resources/asset/document.yaml deleted file mode 100644 index 73367b45c..000000000 --- a/config/api_platform/resources/asset/document.yaml +++ /dev/null @@ -1,14 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Asset\Document: - provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider - operations: - ApiPlatform\Metadata\Get: - normalizationContext: - groups: ['document:read', 'asset:read', 'asset:item:get', 'dependency:read', 'property:read', 'element:read', 'element:item:get' ] - properties: - id: - identifier: true - normalizationContext: - groups: ['document:read', 'asset:read', 'dependency:read', 'property:read', 'element:read'] - denormalizationContext: - groups: ['document:write', 'asset:write'] diff --git a/config/api_platform/resources/asset/image.yaml b/config/api_platform/resources/asset/image.yaml deleted file mode 100644 index 11f47d019..000000000 --- a/config/api_platform/resources/asset/image.yaml +++ /dev/null @@ -1,14 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Asset\Image: - provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider - operations: - ApiPlatform\Metadata\Get: - normalizationContext: - groups: ['image:read', 'asset:read', 'asset:item:get', 'dependency:read', 'property:read', 'element:read', 'element:item:get' ] - properties: - id: - identifier: true - normalizationContext: - groups: ['image:read', 'asset:read', 'dependency:read', 'property:read', 'element:read'] - denormalizationContext: - groups: ['image:write', 'asset:write'] diff --git a/config/api_platform/resources/asset/video.yaml b/config/api_platform/resources/asset/video.yaml deleted file mode 100644 index 56a5cda21..000000000 --- a/config/api_platform/resources/asset/video.yaml +++ /dev/null @@ -1,14 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Asset\Video: - provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider - operations: - ApiPlatform\Metadata\Get: - normalizationContext: - groups: ['video:read', 'asset:read', 'asset:item:get', 'dependency:read', 'property:read', 'element:read', 'element:item:get' ] - properties: - id: - identifier: true - normalizationContext: - groups: ['video:read', 'asset:read', 'dependency:read', 'property:read', 'element:read'] - denormalizationContext: - groups: ['video:write', 'asset:write'] diff --git a/config/api_platform/resources/schedule/task.yaml b/config/api_platform/resources/schedule/task.yaml deleted file mode 100644 index 6f6f2ecd0..000000000 --- a/config/api_platform/resources/schedule/task.yaml +++ /dev/null @@ -1,10 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Task: - provider: Pimcore\Bundle\StudioApiBundle\State\ScheduledTaskProvider - normalizationContext: - groups: ['task:read'] - denormalizationContext: - groups: ['task:write'] - properties: - id: - identifier: true \ No newline at end of file diff --git a/config/api_platform/resources/token/create.yaml b/config/api_platform/resources/token/create.yaml deleted file mode 100644 index 9d4fe37b4..000000000 --- a/config/api_platform/resources/token/create.yaml +++ /dev/null @@ -1,13 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Token\Create: - shortName: Token-Create - operations: - ApiPlatform\Metadata\Post: - processor: Pimcore\Bundle\StudioApiBundle\State\Token\Create\Processor - output: Pimcore\Bundle\StudioApiBundle\Dto\Token\Output - uriTemplate: '/token/create' - openapiContext: - summary: 'Creates and returns a token' - - normalizationContext: - groups: [ 'token:read' ] \ No newline at end of file diff --git a/config/api_platform/resources/token/refresh.yaml b/config/api_platform/resources/token/refresh.yaml deleted file mode 100644 index 42d12ef76..000000000 --- a/config/api_platform/resources/token/refresh.yaml +++ /dev/null @@ -1,13 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Token\Refresh: - shortName: Token-Refresh - operations: - ApiPlatform\Metadata\Post: - processor: Pimcore\Bundle\StudioApiBundle\State\Token\Refresh\Processor - output: Pimcore\Bundle\StudioApiBundle\Dto\Token\Output - uriTemplate: '/token/refresh' - openapiContext: - summary: 'Refreshes an existing token' - - normalizationContext: - groups: [ 'tokenrefresh:read' ] \ No newline at end of file diff --git a/config/api_platform/resources/translation.yaml b/config/api_platform/resources/translation.yaml deleted file mode 100644 index bd7d920b4..000000000 --- a/config/api_platform/resources/translation.yaml +++ /dev/null @@ -1,12 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Translation: - #security: 'is_granted("PUBLIC_API_PLATFORM", "translation")' - operations: - ApiPlatform\Metadata\Post: - processor: Pimcore\Bundle\StudioApiBundle\State\TranslationProcessor - uriTemplate: '/translations' - openapiContext: - summary: 'Getting translations based on context' - - normalizationContext: - groups: [ 'translation:read' ] diff --git a/config/api_platform/resources/user.yaml b/config/api_platform/resources/user.yaml deleted file mode 100644 index 413e307b9..000000000 --- a/config/api_platform/resources/user.yaml +++ /dev/null @@ -1,17 +0,0 @@ -resources: - Pimcore\Model\User: - operations: - ApiPlatform\Metadata\Post: - status: 202 - processor: Pimcore\Bundle\StudioApiBundle\State\ResetPasswordProcessor - input: Pimcore\Bundle\StudioApiBundle\Dto\ResetPasswordRequest - output: false - uriTemplate: '/users/reset-password' - ApiPlatform\Metadata\Get: - normalizationContext: - groups: [ 'user:read' ] - provider: Pimcore\Bundle\StudioApiBundle\State\UserProvider - normalizationContext: - groups: [ 'get' ] - denormalizationContext: - groups: [ 'set' ] \ No newline at end of file diff --git a/config/api_platform/resources/version.yaml b/config/api_platform/resources/version.yaml deleted file mode 100644 index 15478a634..000000000 --- a/config/api_platform/resources/version.yaml +++ /dev/null @@ -1,16 +0,0 @@ -resources: - Pimcore\Bundle\StudioApiBundle\Dto\Version: - operations: - ApiPlatform\Metadata\Get: - ApiPlatform\Metadata\Put: - ApiPlatform\Metadata\Post: - ApiPlatform\Metadata\Delete: - ApiPlatform\Metadata\Patch: - provider: Pimcore\Bundle\StudioApiBundle\State\VersionProvider - normalizationContext: - groups: ['version:read'] - denormalizationContext: - groups: ['version:write'] - properties: - id: - identifier: true \ No newline at end of file diff --git a/config/pimcore/config.yaml b/config/pimcore/config.yaml index 7065d2c51..2e049ab29 100644 --- a/config/pimcore/config.yaml +++ b/config/pimcore/config.yaml @@ -1,23 +1,26 @@ -api_platform: - title: 'Pimcore Studio API' - description: 'API for Pimcore Studio UI' - version: 0.0.1 - graphql: - graphql_playground: false - graphiql: - enabled: false - event_listeners_backward_compatibility_layer: false - keep_legacy_inflector: false - enable_docs: true - enable_entrypoint: false - enable_swagger_ui: false - enable_re_doc: false - show_webby: false - swagger: - api_keys: - access_token: - name: 'Authorization' - type: 'header' +imports: + - { resource: 'packages/nelmio_api_doc.yaml' } + +#api_platform: +# title: 'Pimcore Studio API' +# description: 'API for Pimcore Studio UI' +# version: 0.0.1 +# graphql: +# graphql_playground: false +# graphiql: +# enabled: false +# event_listeners_backward_compatibility_layer: false +# keep_legacy_inflector: false +# enable_docs: true +# enable_entrypoint: false +# enable_swagger_ui: false +# enable_re_doc: false +# show_webby: false +# swagger: +# api_keys: +# access_token: +# name: 'Authorization' +# type: 'header' pimcore: translations: diff --git a/config/pimcore/packages/nelmio_api_doc.yaml b/config/pimcore/packages/nelmio_api_doc.yaml new file mode 100644 index 000000000..e1c6d6869 --- /dev/null +++ b/config/pimcore/packages/nelmio_api_doc.yaml @@ -0,0 +1,14 @@ +nelmio_api_doc: + models: + names: + - { alias: Translation, type: Pimcore\Bundle\StudioApiBundle\Dto\Translation } + areas: + path_patterns: # an array of regexps (document only routes under /api, except /api/doc) + - ^/api(?!/doc$) + host_patterns: # document only routes with a host of the form api.* + - ^api\. + documentation: + info: + title: My API + description: My API Description + version: 1.0.0 diff --git a/config/pimcore/routing.yaml b/config/pimcore/routing.yaml index 961a5e3b5..8916e04a7 100644 --- a/config/pimcore/routing.yaml +++ b/config/pimcore/routing.yaml @@ -1,4 +1,18 @@ api_platform: resource: . type: api_platform - prefix: /api \ No newline at end of file + prefix: /testtesttest + + +api_platform_swagger_ui: + path: /api/doc/swagger + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger_ui } + + +studio_api: + resource: "../../src/Controller/Api" + type: annotation + prefix: /api + options: + expose: true \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 83bf2f682..991f69940 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -109,8 +109,8 @@ services: - { name: security.voter } #Decorators - Pimcore\Bundle\StudioApiBundle\ApiPlatform\OpenApiFactoryDecorator: - decorates: 'api_platform.openapi.factory' + #Pimcore\Bundle\StudioApiBundle\ApiPlatform\OpenApiFactoryDecorator: + #decorates: 'api_platform.openapi.factory' #Service Locator generic_data_index.asset_hydrator.service_locator: @@ -126,3 +126,8 @@ services: Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Asset\SearchResult\SearchResultItem\Text: '@Pimcore\Bundle\StudioApiBundle\Service\GenericData\V1\Hydrator\Asset\TextHydratorInterface' Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Asset\SearchResult\SearchResultItem\Unknown: '@Pimcore\Bundle\StudioApiBundle\Service\GenericData\V1\Hydrator\Asset\UnknownHydratorInterface' Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Asset\SearchResult\SearchResultItem\Video: '@Pimcore\Bundle\StudioApiBundle\Service\GenericData\V1\Hydrator\Asset\VideoHydratorInterface' + + + Pimcore\Bundle\StudioApiBundle\EventSubscriber\ApiExceptionListener: + tags: + - { name: kernel.event_subscriber } \ No newline at end of file diff --git a/src/ApiPlatform/OpenApiFactoryDecorator.php b/src/ApiPlatform/OpenApiFactoryDecorator.php deleted file mode 100644 index 021f1e910..000000000 --- a/src/ApiPlatform/OpenApiFactoryDecorator.php +++ /dev/null @@ -1,44 +0,0 @@ -decorated->__invoke($context); - - $securitySchemes = $openApi->getComponents()->getSecuritySchemes() ?: new ArrayObject(); - $securitySchemes[self::ACCESS_TOKEN] = new SecurityScheme( - type: 'http', - scheme: 'bearer', - ); - - return $openApi; - } -} diff --git a/src/Controller/Api/AbstractApiController.php b/src/Controller/Api/AbstractApiController.php new file mode 100644 index 000000000..42d10a21d --- /dev/null +++ b/src/Controller/Api/AbstractApiController.php @@ -0,0 +1,12 @@ +getKeys())) { + return new JsonResponse($this->translatorService->getAllTranslations($translation->getLocale())); + } + return new JsonResponse($this->translatorService->getTranslationsForKeys( + $translation->getLocale(), + $translation->getKeys() + )); + } +} \ No newline at end of file diff --git a/src/DependencyInjection/PimcoreStudioApiExtension.php b/src/DependencyInjection/PimcoreStudioApiExtension.php index 9d6246b87..0e263cb83 100644 --- a/src/DependencyInjection/PimcoreStudioApiExtension.php +++ b/src/DependencyInjection/PimcoreStudioApiExtension.php @@ -22,7 +22,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Yaml\Yaml; /** * This is the class that loads and manages your bundle configuration. @@ -64,15 +66,33 @@ public function load(array $configs, ContainerBuilder $container): void $definition->setArgument('$tokenLifetime', $config['api_token']['lifetime']); } + /** + * @throws Exception + */ public function prepend(ContainerBuilder $container): void { - $apiPlatformConfig = [ - 'mapping'=>[ - 'paths'=> [ - __DIR__ . '/../../config/api_platform/', - ], + $paths = $this->getPaths(); + + if(empty($paths)) { + return; + } + + $container->prependExtensionConfig('nelmio_api_doc', [ + 'documentation' => [ + 'paths' => $paths, ], - ]; - $container->prependExtensionConfig('api_platform', $apiPlatformConfig); + ]); + } + + private function getPaths(): array + { + $finder = new Finder(); + $finder->files()->in(__DIR__ . '/../../config/api')->name('*.yaml'); + $paths = []; + foreach($finder as $file) { + $paths = [...$paths, ...Yaml::parseFile($file->getRealPath())]; + } + + return $paths; } } diff --git a/src/Dto/Translation.php b/src/Dto/Translation.php index a3afa84e5..4ea9b722c 100644 --- a/src/Dto/Translation.php +++ b/src/Dto/Translation.php @@ -16,6 +16,8 @@ namespace Pimcore\Bundle\StudioApiBundle\Dto; +use Pimcore\Bundle\StudioApiBundle\Util\Constants\PublicTranslations; + /** * @internal */ @@ -23,7 +25,7 @@ { public function __construct( private string $locale = 'en', - private array $keys = [] + private array $keys = PublicTranslations::PUBLIC_KEYS ) { } diff --git a/src/EventSubscriber/ApiExceptionListener.php b/src/EventSubscriber/ApiExceptionListener.php new file mode 100644 index 000000000..3c53af9f9 --- /dev/null +++ b/src/EventSubscriber/ApiExceptionListener.php @@ -0,0 +1,35 @@ + ['onKernelException', 2], + ]; + } + + public function onKernelException(ExceptionEvent $event): void + { + $exception = $event->getThrowable(); + + // ... perform some action (e.g. logging) + + // optionally set the custom response + $event->setResponse(new Response($exception->getMessage(), $exception->getCode())); + + // or stop propagation (prevents the next exception listeners from being called) + //$event->stopPropagation(); + } +} \ No newline at end of file diff --git a/src/PimcoreStudioApiBundle.php b/src/PimcoreStudioApiBundle.php index 7b31faf9b..5dc6ded12 100644 --- a/src/PimcoreStudioApiBundle.php +++ b/src/PimcoreStudioApiBundle.php @@ -17,6 +17,7 @@ namespace Pimcore\Bundle\StudioApiBundle; use ApiPlatform\Symfony\Bundle\ApiPlatformBundle; +use Nelmio\ApiDocBundle\NelmioApiDocBundle; use Pimcore\Extension\Bundle\AbstractPimcoreBundle; use Pimcore\Extension\Bundle\Installer\InstallerInterface; use Pimcore\HttpKernel\Bundle\DependentBundleInterface; @@ -51,5 +52,6 @@ public function getInstaller(): ?InstallerInterface public static function registerDependentBundles(BundleCollection $collection): void { $collection->addBundle(new ApiPlatformBundle()); + $collection->addBundle(new NelmioApiDocBundle()); } } diff --git a/src/Security/Trait/PublicTranslationTrait.php b/src/Security/Trait/PublicTranslationTrait.php index d3c082822..69ff48273 100644 --- a/src/Security/Trait/PublicTranslationTrait.php +++ b/src/Security/Trait/PublicTranslationTrait.php @@ -44,7 +44,8 @@ private function voteOnTranslation(InputBag $payload): bool sprintf( 'You have requested non public keys: %s', implode(',', $nonPublicTranslations) - ) + ), + 403 ); } diff --git a/src/Security/Voter/PublicTokenVoter.php b/src/Security/Voter/PublicTokenVoter.php index a1a61a2c5..a87223973 100644 --- a/src/Security/Voter/PublicTokenVoter.php +++ b/src/Security/Voter/PublicTokenVoter.php @@ -24,6 +24,7 @@ use Pimcore\Bundle\StudioApiBundle\Service\SecurityServiceInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -44,7 +45,7 @@ public function __construct( protected function supports(string $attribute, mixed $subject): bool { - return $attribute === self::SUPPORTED_ATTRIBUTE && in_array((string)$subject, self::SUPPORTED_SUBJECTS, true); + return $attribute === self::SUPPORTED_ATTRIBUTE; } /** @@ -53,13 +54,12 @@ protected function supports(string $attribute, mixed $subject): bool */ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { - $request = $this->getCurrentRequest($this->requestStack); try { $authToken = $this->getAuthToken($request); } catch (NoAuthTokenFound) { - return $this->voteOnRequest($request, $subject); + return $this->voteOnRequest($request, $subject->metadata->getName()); } if ($this->securityService->checkAuthToken($authToken)) { diff --git a/src/Service/TranslatorService.php b/src/Service/TranslatorService.php index 7d18e41dc..98fa28684 100644 --- a/src/Service/TranslatorService.php +++ b/src/Service/TranslatorService.php @@ -40,21 +40,19 @@ public function __construct( $this->translator = $translator; } - public function getAllTranslations(string $locale): Translation + public function getAllTranslations(string $locale): array { - return new Translation( - $locale, - $this->translator->getCatalogue($locale)->all(self::DOMAIN) - ); + $translations = $this->translator->getCatalogue($locale)->all(self::DOMAIN); + return ['locale' => $locale, 'keys' => $translations]; } - public function getTranslationsForKeys(string $locale, array $keys): Translation + public function getTranslationsForKeys(string $locale, array $keys): array { $translations = []; foreach ($keys as $key) { $translations[$key] = $this->translator->getCatalogue($locale)->get($key, self::DOMAIN); } - return new Translation($locale, $translations); + return ['locale' => $locale, 'keys' => $translations]; } } diff --git a/src/Service/TranslatorServiceInterface.php b/src/Service/TranslatorServiceInterface.php index d8b7cab96..266ee0a23 100644 --- a/src/Service/TranslatorServiceInterface.php +++ b/src/Service/TranslatorServiceInterface.php @@ -23,7 +23,7 @@ */ interface TranslatorServiceInterface { - public function getAllTranslations(string $locale): Translation; + public function getAllTranslations(string $locale): array; - public function getTranslationsForKeys(string $locale, array $keys): Translation; + public function getTranslationsForKeys(string $locale, array $keys): array; } diff --git a/translations/studio.de.yaml b/translations/studio.de.yaml new file mode 100644 index 000000000..4f3a18ab2 --- /dev/null +++ b/translations/studio.de.yaml @@ -0,0 +1 @@ +test: DEUTSCH \ No newline at end of file diff --git a/translations/studio.en.yaml b/translations/studio.en.yaml new file mode 100644 index 000000000..105e263d6 --- /dev/null +++ b/translations/studio.en.yaml @@ -0,0 +1 @@ +test: ENGLISH \ No newline at end of file