From 1329c2784782f0ebfa1f6aaf279c4b326f2d8877 Mon Sep 17 00:00:00 2001 From: Tomasz Kryszan Date: Tue, 29 Aug 2023 15:32:28 +0200 Subject: [PATCH] Added PermissionLimitationResolver --- phpstan-baseline.neon | 15 +- .../LanguageLimitationController.php | 40 +++ src/bundle/Resources/config/routing.yaml | 11 + .../Resources/config/services/components.yaml | 7 + .../config/services/controllers.yaml | 6 + .../config/services/permissions.yaml | 5 + .../config/services/ui_config/common.yaml | 2 + .../views/themes/admin/ui/html_body.html.twig | 22 ++ .../views/themes/admin/ui/layout.html.twig | 2 + .../Twig/EmbeddedItemEditFormExtension.php | 52 ++++ .../Permission/PermissionCheckerInterface.php | 14 +- src/lib/Permission/LimitationResolver.php | 171 ++++++++++ .../LimitationResolverInterface.php | 45 +++ src/lib/Permission/PermissionChecker.php | 113 +------ .../EmbeddedItemEditFormExtensionTest.php | 100 ++++++ .../ibexa_render_embedded_item_edit_form.test | 9 + .../lib/Permission/LimitationResolverTest.php | 294 ++++++++++++++++++ .../lib/Permission/PermissionCheckerTest.php | 34 +- 18 files changed, 806 insertions(+), 136 deletions(-) create mode 100644 src/bundle/Controller/Permission/LanguageLimitationController.php create mode 100644 src/bundle/Resources/views/themes/admin/ui/html_body.html.twig create mode 100644 src/bundle/Templating/Twig/EmbeddedItemEditFormExtension.php create mode 100644 src/lib/Permission/LimitationResolver.php create mode 100644 src/lib/Permission/LimitationResolverInterface.php create mode 100644 tests/bundle/Templating/Twig/EmbeddedItemEditFormExtensionTest.php create mode 100644 tests/bundle/Templating/Twig/_fixtures/render_embedded_item_edit_form/ibexa_render_embedded_item_edit_form.test create mode 100644 tests/lib/Permission/LimitationResolverTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 96f0819df2..a5551137e8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -8630,6 +8630,11 @@ parameters: count: 1 path: src/lib/Pagination/Pagerfanta/URLWildcardAdapter.php + - + message: "#^Access to protected property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\LookupLimitationResult\\:\\:\\$hasAccess\\.$#" + count: 1 + path: src/lib/Permission/LimitationResolver.php + - message: "#^Access to protected property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\LookupLimitationResult\\:\\:\\$lookupPolicyLimitations\\.$#" count: 2 @@ -8685,16 +8690,6 @@ parameters: count: 1 path: src/lib/Permission/PermissionChecker.php - - - message: "#^Parameter \\#1 \\$array of function array_filter expects array, iterable\\ given\\.$#" - count: 1 - path: src/lib/Permission/PermissionChecker.php - - - - message: "#^Parameter \\#1 \\$contentTypeIds of method Ibexa\\\\Contracts\\\\Core\\\\Limitation\\\\Target\\\\Builder\\\\VersionBuilder\\:\\:createFromAnyContentTypeOf\\(\\) expects array\\, array\\ given\\.$#" - count: 2 - path: src/lib/Permission/PermissionChecker.php - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" count: 1 diff --git a/src/bundle/Controller/Permission/LanguageLimitationController.php b/src/bundle/Controller/Permission/LanguageLimitationController.php new file mode 100644 index 0000000000..054e5a0df1 --- /dev/null +++ b/src/bundle/Controller/Permission/LanguageLimitationController.php @@ -0,0 +1,40 @@ +limitationResolver = $limitationResolver; + } + + public function loadLanguageLimitationsForContentAction( + Content $content, + ?VersionInfo $versionInfo = null, + ?Location $location = null + ): Response { + return new JsonResponse( + $this->limitationResolver->getLanguageLimitations( + $versionInfo ?? $content->getVersionInfo(), + $location ?? $content->getVersionInfo()->getContentInfo()->getMainLocation() + ) + ); + } +} diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index eaee6cf806..e67588de01 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -958,3 +958,14 @@ ibexa.asset.upload_image: defaults: _controller: 'Ibexa\Bundle\AdminUi\Controller\AssetController::uploadImageAction' methods: [POST] + +# +# Permissions +# +ibexa.permission.limitation.language: + path: permission/limitation/language/{contentId} + options: + expose: true + defaults: + _controller: 'Ibexa\Bundle\AdminUi\Controller\Permission\LanguageLimitationController::loadLanguageLimitationsForContentAction' + methods: [GET] diff --git a/src/bundle/Resources/config/services/components.yaml b/src/bundle/Resources/config/services/components.yaml index 6133d6f215..d8cc01704a 100644 --- a/src/bundle/Resources/config/services/components.yaml +++ b/src/bundle/Resources/config/services/components.yaml @@ -39,3 +39,10 @@ services: public: false Ibexa\Contracts\AdminUi\Component\Renderer\RendererInterface: '@Ibexa\AdminUi\Component\Renderer\DefaultRenderer' + + ibexa.adminui.html.body: + parent: Ibexa\AdminUi\Component\TwigComponent + arguments: + $template: '@@ibexadesign/ui/html_body.html.twig' + tags: + - { name: ibexa.admin_ui.component, group: 'html-body' } diff --git a/src/bundle/Resources/config/services/controllers.yaml b/src/bundle/Resources/config/services/controllers.yaml index 8dcc72755a..5f269e81d7 100644 --- a/src/bundle/Resources/config/services/controllers.yaml +++ b/src/bundle/Resources/config/services/controllers.yaml @@ -212,3 +212,9 @@ services: autowire: true Ibexa\Bundle\AdminUi\Controller\User\InvitationController: ~ + + Ibexa\Bundle\AdminUi\Controller\Permission\LanguageLimitationController: + parent: Ibexa\Contracts\AdminUi\Controller\Controller + autowire: true + tags: + - controller.service_arguments diff --git a/src/bundle/Resources/config/services/permissions.yaml b/src/bundle/Resources/config/services/permissions.yaml index 203a4748cb..c152f57d37 100644 --- a/src/bundle/Resources/config/services/permissions.yaml +++ b/src/bundle/Resources/config/services/permissions.yaml @@ -10,3 +10,8 @@ services: alias: Ibexa\AdminUi\Permission\PermissionChecker Ibexa\AdminUi\Permission\LookupLimitationsTransformer: ~ + + Ibexa\AdminUi\Permission\LimitationResolver: ~ + + Ibexa\AdminUi\Permission\LimitationResolverInterface: + alias: Ibexa\AdminUi\Permission\LimitationResolver diff --git a/src/bundle/Resources/config/services/ui_config/common.yaml b/src/bundle/Resources/config/services/ui_config/common.yaml index 316a31fc35..8ba39dd7c1 100644 --- a/src/bundle/Resources/config/services/ui_config/common.yaml +++ b/src/bundle/Resources/config/services/ui_config/common.yaml @@ -96,6 +96,8 @@ services: Ibexa\Bundle\AdminUi\Templating\Twig\ContentTypeIconExtension: ~ + Ibexa\Bundle\AdminUi\Templating\Twig\EmbeddedItemEditFormExtension: ~ + Ibexa\AdminUi\UI\Config\Provider\UserContentTypes: tags: - { name: ibexa.admin_ui.config.provider, key: 'userContentTypes' } diff --git a/src/bundle/Resources/views/themes/admin/ui/html_body.html.twig b/src/bundle/Resources/views/themes/admin/ui/html_body.html.twig new file mode 100644 index 0000000000..4e7eec2e56 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/html_body.html.twig @@ -0,0 +1,22 @@ +
+ {% set form = ibexa_render_embedded_item_edit_form() %} + + {{ form_start(form) }} + {{ form_widget(form.content_info, { 'attr': { + 'hidden': 'hidden', + 'class': 'ibexa-embedded-item-edit__form-field ibexa-embedded-item-edit__form-field--content-info' + } }) }} + {{ form_widget(form.version_info, { 'attr': { + 'hidden': 'hidden', + 'class': 'ibexa-embedded-item-edit__form-field ibexa-embedded-item-edit__form-field--version-info' + } }) }} + {{ form_widget(form.language, { 'attr': { + 'hidden': 'hidden', + 'class': 'ibexa-embedded-item-edit__form-field ibexa-embedded-item-edit__form-field--language' + } }) }} + {{ form_widget(form.location, { 'attr': { + 'hidden': 'hidden', + 'class': 'ibexa-embedded-item-edit__form-field ibexa-embedded-item-edit__form-field--location' + } }) }} + {{ form_end(form) }} +
diff --git a/src/bundle/Resources/views/themes/admin/ui/layout.html.twig b/src/bundle/Resources/views/themes/admin/ui/layout.html.twig index bc485c543d..4215a13f9f 100644 --- a/src/bundle/Resources/views/themes/admin/ui/layout.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/layout.html.twig @@ -221,6 +221,8 @@ {% endblock %} {% endif %} + {{ ibexa_render_component_group('html-body') }} + {{ encore_entry_script_tags('ibexa-admin-ui-layout-js', null, 'ibexa') }} {{ encore_entry_script_tags('ibexa-admin-ui-udw-tabs-js', null, 'ibexa') }} {{ encore_entry_script_tags('ibexa-admin-ui-udw-extras-js', null, 'ibexa') }} diff --git a/src/bundle/Templating/Twig/EmbeddedItemEditFormExtension.php b/src/bundle/Templating/Twig/EmbeddedItemEditFormExtension.php new file mode 100644 index 0000000000..2ea759d786 --- /dev/null +++ b/src/bundle/Templating/Twig/EmbeddedItemEditFormExtension.php @@ -0,0 +1,52 @@ +formFactory = $formFactory; + $this->router = $router; + } + + public function getFunctions(): array + { + return [ + new TwigFunction( + 'ibexa_render_embedded_item_edit_form', + [$this, 'renderEmbeddedItemEditForm'] + ), + ]; + } + + public function renderEmbeddedItemEditForm(): FormView + { + return $this->formFactory->contentEdit( + new ContentEditData(), + 'embedded_item_edit', + [ + 'action' => $this->router->generate('ibexa.content.edit'), + ] + )->createView(); + } +} diff --git a/src/contracts/Permission/PermissionCheckerInterface.php b/src/contracts/Permission/PermissionCheckerInterface.php index 23befd3c65..d8859d2211 100644 --- a/src/contracts/Permission/PermissionCheckerInterface.php +++ b/src/contracts/Permission/PermissionCheckerInterface.php @@ -19,20 +19,26 @@ public function getRestrictions(array $hasAccess, string $class): array; public function canCreateInLocation(Location $location, $hasAccess): bool; /** - * @internal - * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * + * @internal + * + * @deprecated 4.6.0 The "\Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface::getContentCreateLimitations()" method is deprecated, will be removed in 5.0. + * Use { @see \Ibexa\AdminUi\Permission\LimitationResolverInterface::getContentCreateLimitations } instead. */ public function getContentCreateLimitations(Location $parentLocation): LookupLimitationResult; /** - * @internal - * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * + * @internal + * + * @deprecated 4.6.0 The "\Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface::getContentUpdateLimitations()" method is deprecated, will be removed in 5.0. + * Use { @see \Ibexa\AdminUi\Permission\LimitationResolverInterface::getContentUpdateLimitations } instead. */ public function getContentUpdateLimitations(Location $parentLocation): LookupLimitationResult; } diff --git a/src/lib/Permission/LimitationResolver.php b/src/lib/Permission/LimitationResolver.php new file mode 100644 index 0000000000..a8e651f001 --- /dev/null +++ b/src/lib/Permission/LimitationResolver.php @@ -0,0 +1,171 @@ +contentService = $contentService; + $this->contentTypeService = $contentTypeService; + $this->languageService = $languageService; + $this->locationService = $locationService; + $this->lookupLimitationsTransformer = $lookupLimitationsTransformer; + $this->permissionResolver = $permissionResolver; + } + + public function getContentCreateLimitations(Location $parentLocation): LookupLimitationResult + { + $contentInfo = $parentLocation->getContentInfo(); + $contentType = $this->contentTypeService->loadContentType($contentInfo->getContentType()->id); + $contentCreateStruct = $this->contentService->newContentCreateStruct($contentType, $contentInfo->getMainLanguageCode()); + $contentCreateStruct->sectionId = $contentInfo->getSection(); + $locationCreateStruct = $this->locationService->newLocationCreateStruct($parentLocation->id); + + $versionBuilder = new VersionBuilder(); + $versionBuilder->translateToAnyLanguageOf($this->getActiveLanguageCodes()); + $versionBuilder->createFromAnyContentTypeOf($this->getContentTypeIds()); + + return $this->permissionResolver->lookupLimitations( + 'content', + 'create', + $contentCreateStruct, + [$versionBuilder->build(), $locationCreateStruct], + [Limitation::CONTENTTYPE, Limitation::LANGUAGE] + ); + } + + public function getContentUpdateLimitations(Location $parentLocation): LookupLimitationResult + { + $versionBuilder = new VersionBuilder(); + $versionBuilder->translateToAnyLanguageOf($this->getActiveLanguageCodes()); + $versionBuilder->createFromAnyContentTypeOf($this->getContentTypeIds()); + + return $this->permissionResolver->lookupLimitations( + 'content', + 'edit', + $parentLocation->getContentInfo(), + [$versionBuilder->build(), $parentLocation], + [Limitation::CONTENTTYPE, Limitation::LANGUAGE] + ); + } + + public function getLanguageLimitations( + VersionInfo $versionInfo, + ?Location $location = null + ): array { + $languages = $versionInfo->getLanguages(); + $targets[] = (new VersionBuilder())->translateToAnyLanguageOf($this->getActiveLanguageCodes($languages))->build(); + if (null !== $location) { + $targets[] = $location; + } + + $lookupLimitations = $this->permissionResolver->lookupLimitations( + 'content', + 'edit', + $versionInfo->getContentInfo(), + $targets, + [Limitation::LANGUAGE] + ); + + $limitationLanguageCodes = $this->lookupLimitationsTransformer->getFlattenedLimitationsValues($lookupLimitations); + + $languageLimitations = []; + foreach ($languages as $language) { + $languageLimitations[] = [ + 'languageCode' => $language->getLanguageCode(), + 'name' => $language->getName(), + 'hasAccess' => $lookupLimitations->hasAccess && $this->hasAccessToLanguage($language, $limitationLanguageCodes), + ]; + } + + return $languageLimitations; + } + + /** + * @param array $limitationLanguageCodes + */ + private function hasAccessToLanguage(Language $language, array $limitationLanguageCodes): bool + { + return $language->isEnabled() + && ( + empty($limitationLanguageCodes) + || in_array($language->getLanguageCode(), $limitationLanguageCodes, true) + ); + } + + /** + * @return array + */ + private function getContentTypeIds(): array + { + $contentTypeIds = []; + + $contentTypeGroups = $this->contentTypeService->loadContentTypeGroups(); + foreach ($contentTypeGroups as $contentTypeGroup) { + $contentTypes = $this->contentTypeService->loadContentTypes($contentTypeGroup); + foreach ($contentTypes as $contentType) { + $contentTypeIds[] = $contentType->id; + } + } + + return $contentTypeIds; + } + + /** + * @param iterable<\Ibexa\Contracts\Core\Repository\Values\Content\Language>|null $languages + * + * @return array + */ + private function getActiveLanguageCodes(?iterable $languages = null): array + { + $languageCodes = []; + $languages ??= $this->languageService->loadLanguages(); + foreach ($languages as $language) { + if ($language->isEnabled()) { + $languageCodes[] = $language->getLanguageCode(); + } + } + + return $languageCodes; + } +} diff --git a/src/lib/Permission/LimitationResolverInterface.php b/src/lib/Permission/LimitationResolverInterface.php new file mode 100644 index 0000000000..0219e4ed1b --- /dev/null +++ b/src/lib/Permission/LimitationResolverInterface.php @@ -0,0 +1,45 @@ + + */ + public function getLanguageLimitations( + VersionInfo $versionInfo, + ?Location $location = null + ): array; +} diff --git a/src/lib/Permission/PermissionChecker.php b/src/lib/Permission/PermissionChecker.php index 1c7b2f4e99..0f30f971de 100644 --- a/src/lib/Permission/PermissionChecker.php +++ b/src/lib/Permission/PermissionChecker.php @@ -9,16 +9,9 @@ namespace Ibexa\AdminUi\Permission; use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface; -use Ibexa\Contracts\Core\Limitation\Target\Builder\VersionBuilder; -use Ibexa\Contracts\Core\Repository\ContentService; -use Ibexa\Contracts\Core\Repository\ContentTypeService; -use Ibexa\Contracts\Core\Repository\LanguageService; -use Ibexa\Contracts\Core\Repository\LocationService; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; -use Ibexa\Contracts\Core\Repository\Values\Content\Language; use Ibexa\Contracts\Core\Repository\Values\Content\Location; -use Ibexa\Contracts\Core\Repository\Values\User\Limitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\LocationLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\ParentContentTypeLimitation; use Ibexa\Contracts\Core\Repository\Values\User\Limitation\ParentDepthLimitation; @@ -39,40 +32,16 @@ class PermissionChecker implements PermissionCheckerInterface /** @var \Ibexa\Contracts\Core\Repository\UserService */ private $userService; - /** @var \Ibexa\Contracts\Core\Repository\LocationService */ - private $locationService; + private LimitationResolverInterface $limitationResolver; - /** @var \Ibexa\Contracts\Core\Repository\ContentTypeService */ - private $contentTypeService; - - /** @var \Ibexa\Contracts\Core\Repository\ContentService */ - private $contentService; - - /** @var \Ibexa\Contracts\Core\Repository\LanguageService */ - private $languageService; - - /** - * @param \Ibexa\Contracts\Core\Repository\PermissionResolver $permissionResolver - * @param \Ibexa\Contracts\Core\Repository\UserService $userService - * @param \Ibexa\Contracts\Core\Repository\LocationService $locationService - * @param \Ibexa\Contracts\Core\Repository\ContentService $contentService - * @param \Ibexa\Contracts\Core\Repository\ContentTypeService $contentTypeService - * @param \Ibexa\Contracts\Core\Repository\LanguageService $languageService - */ public function __construct( PermissionResolver $permissionResolver, - UserService $userService, - LocationService $locationService, - ContentService $contentService, - ContentTypeService $contentTypeService, - LanguageService $languageService + LimitationResolverInterface $limitationResolver, + UserService $userService ) { $this->permissionResolver = $permissionResolver; + $this->limitationResolver = $limitationResolver; $this->userService = $userService; - $this->locationService = $locationService; - $this->contentTypeService = $contentTypeService; - $this->contentService = $contentService; - $this->languageService = $languageService; } /** @@ -190,39 +159,24 @@ public function canCreateInLocation(Location $location, $hasAccess): bool public function getContentCreateLimitations(Location $parentLocation): LookupLimitationResult { - $contentType = $this->contentTypeService->loadContentType($parentLocation->contentInfo->contentTypeId); - $contentCreateStruct = $this->contentService->newContentCreateStruct($contentType, $parentLocation->contentInfo->mainLanguageCode); - $contentCreateStruct->sectionId = $parentLocation->contentInfo->sectionId; - $locationCreateStruct = $this->locationService->newLocationCreateStruct($parentLocation->id); - - $versionBuilder = new VersionBuilder(); - $versionBuilder->translateToAnyLanguageOf($this->getActiveLanguageCodes()); - $versionBuilder->createFromAnyContentTypeOf($this->getContentTypeIds()); - - return $this->permissionResolver->lookupLimitations( - 'content', - 'create', - $contentCreateStruct, - [$versionBuilder->build(), $locationCreateStruct], - [Limitation::CONTENTTYPE, Limitation::LANGUAGE] + trigger_deprecation( + 'ibexa/admin-ui', + '4.6', + sprintf('The %s() method is deprecated, will be removed in 5.0.', __METHOD__) ); + + return $this->limitationResolver->getContentCreateLimitations($parentLocation); } public function getContentUpdateLimitations(Location $location): LookupLimitationResult { - $contentInfo = $location->getContentInfo(); - - $versionBuilder = new VersionBuilder(); - $versionBuilder->translateToAnyLanguageOf($this->getActiveLanguageCodes()); - $versionBuilder->createFromAnyContentTypeOf($this->getContentTypeIds()); - - return $this->permissionResolver->lookupLimitations( - 'content', - 'edit', - $contentInfo, - [$versionBuilder->build(), $location], - [Limitation::CONTENTTYPE, Limitation::LANGUAGE] + trigger_deprecation( + 'ibexa/admin-ui', + '4.6', + sprintf('The %s() method is deprecated, will be removed in 5.0.', __METHOD__) ); + + return $this->limitationResolver->getContentUpdateLimitations($location); } /** @@ -299,39 +253,6 @@ private function loadAllUserGroupsIdsOfUser(User $user): array return $allUserGroups; } - - /** - * @return string[] - */ - private function getActiveLanguageCodes(): array - { - $filter = array_filter( - $this->languageService->loadLanguages(), - static function (Language $language) { - return $language->enabled; - } - ); - - return array_column($filter, 'languageCode'); - } - - /** - * @return string[] - */ - private function getContentTypeIds(): array - { - $contentTypeIds = []; - - $contentTypeGroups = $this->contentTypeService->loadContentTypeGroups(); - foreach ($contentTypeGroups as $contentTypeGroup) { - $contentTypes = $this->contentTypeService->loadContentTypes($contentTypeGroup); - foreach ($contentTypes as $contentType) { - $contentTypeIds[] = $contentType->id; - } - } - - return $contentTypeIds; - } } -class_alias(PermissionChecker::class, 'EzSystems\EzPlatformAdminUi\Permission\PermissionChecker'); +class_alias(PermissionChecker::class, 'EzSystems\EzPlatformAdminUi\Permission\PermissionChecker'); \ No newline at end of file diff --git a/tests/bundle/Templating/Twig/EmbeddedItemEditFormExtensionTest.php b/tests/bundle/Templating/Twig/EmbeddedItemEditFormExtensionTest.php new file mode 100644 index 0000000000..7727e7ade4 --- /dev/null +++ b/tests/bundle/Templating/Twig/EmbeddedItemEditFormExtensionTest.php @@ -0,0 +1,100 @@ +createFormFactory(), + $this->createRouter() + ), + ]; + } + + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration( + $file, + $message, + $condition, + $templates, + $exception, + $outputs, + $deprecation = '' + ): void { + // disable Twig legacy integration test to avoid producing risky warning + self::markTestSkipped('This package does not contain Twig legacy integration test cases'); + } + + protected function getFixturesDir(): string + { + return __DIR__ . '/_fixtures/render_embedded_item_edit_form/'; + } + + private function createEditForm(): FormInterface + { + $editForm = $this->createMock(FormInterface::class); + $editForm + ->expects(self::once()) + ->method('createView') + ->willReturn( + $this->createMock(FormView::class) + ); + + return $editForm; + } + + private function createFormFactory(): FormFactory + { + $formFactory = $this->createMock(FormFactory::class); + $formFactory + ->expects(self::atLeastOnce()) + ->method('contentEdit') + ->with( + new ContentEditData(), + 'embedded_item_edit', + [ + 'action' => self::FORM_ACTION, + ] + ) + ->willReturn($this->createEditForm()); + + return $formFactory; + } + + private function createRouter(): RouterInterface + { + $router = $this->createMock(RouterInterface::class); + $router + ->expects(self::once()) + ->method('generate') + ->with('ibexa.content.edit') + ->willReturn(self::FORM_ACTION); + + return $router; + } +} diff --git a/tests/bundle/Templating/Twig/_fixtures/render_embedded_item_edit_form/ibexa_render_embedded_item_edit_form.test b/tests/bundle/Templating/Twig/_fixtures/render_embedded_item_edit_form/ibexa_render_embedded_item_edit_form.test new file mode 100644 index 0000000000..d6679856e7 --- /dev/null +++ b/tests/bundle/Templating/Twig/_fixtures/render_embedded_item_edit_form/ibexa_render_embedded_item_edit_form.test @@ -0,0 +1,9 @@ +--TEST-- +"ibexa_render_embedded_item_edit_form" function +--TEMPLATE-- +{% set form = ibexa_render_embedded_item_edit_form() %} +{% if form is defined %} YES {% else %} NO {% endif %} +--DATA-- +return []; +--EXPECT-- +YES diff --git a/tests/lib/Permission/LimitationResolverTest.php b/tests/lib/Permission/LimitationResolverTest.php new file mode 100644 index 0000000000..325c205ea1 --- /dev/null +++ b/tests/lib/Permission/LimitationResolverTest.php @@ -0,0 +1,294 @@ +permissionResolver = $this->createMock(PermissionResolver::class); + + $this->limitationResolver = new LimitationResolver( + $this->createMock(ContentService::class), + $this->createMock(ContentTypeService::class), + $this->createMock(LanguageService::class), + $this->createMock(LocationService::class), + new LookupLimitationsTransformer(), + $this->permissionResolver + ); + } + + /** + * @dataProvider provideDataForTestGetLanguageLimitations + * + * @param array $expected + */ + public function testGetLanguageLimitations( + array $expected, + VersionInfo $versionInfo, + Location $location, + LookupLimitationResult $lookupLimitationResult + ): void { + $this->mockPermissionResolverLookupLimitations( + $versionInfo->getContentInfo(), + $location, + $lookupLimitationResult + ); + self::assertEquals( + $expected, + $this->limitationResolver->getLanguageLimitations( + $versionInfo, + $location + ) + ); + } + + /** + * @return iterable, + * \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo, + * \Ibexa\Contracts\Core\Repository\Values\Content\Location, + * \Ibexa\Contracts\Core\Repository\Values\User\LookupLimitationResult, + * }> + */ + public function provideDataForTestGetLanguageLimitations(): iterable + { + $english = $this->createLanguage(1, true, 'eng-GB', 'English'); + $german = $this->createLanguage(2, true, 'ger-DE', 'German'); + $french = $this->createLanguage(3, false, 'fra-FR', 'French'); + $versionInfo = $this->createVersionInfo( + $this->createContentInfo(), + [ + $english, + $german, + $french, + ] + ); + $location = $this->createLocation(); + + yield 'No access to all languages' => [ + [ + $this->getLanguageAccessData(false, $english), + $this->getLanguageAccessData(false, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult(false), + ]; + + yield 'Access to all enabled languages' => [ + [ + $this->getLanguageAccessData(true, $english), + $this->getLanguageAccessData(true, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult(true), + ]; + + yield 'Limited access to English language by policy limitation' => [ + [ + $this->getLanguageAccessData(true, $english), + $this->getLanguageAccessData(false, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult( + true, + [], + [ + new LookupPolicyLimitations( + $this->createMock(Policy::class), + [ + $this->createLanguageLimitation(['eng-GB']), + ] + ), + ] + ), + ]; + + yield 'Limited access to German language by role limitation' => [ + [ + $this->getLanguageAccessData(false, $english), + $this->getLanguageAccessData(true, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult( + true, + [ + $this->createLanguageLimitation(['ger-DE']), + ], + ), + ]; + + yield 'Limited access to English and German languages by role and policy limitations' => [ + [ + $this->getLanguageAccessData(true, $english), + $this->getLanguageAccessData(true, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult( + true, + [ + $this->createLanguageLimitation(['eng-GB', 'fra-FR']), + ], + [ + new LookupPolicyLimitations( + $this->createMock(Policy::class), + [ + $this->createLanguageLimitation(['ger-DE', 'fra-FR']), + ] + ), + ] + ), + ]; + } + + private function createContentInfo(): ContentInfo + { + return $this->createMock(ContentInfo::class); + } + + /** + * @param iterable<\Ibexa\Contracts\Core\Repository\Values\Content\Language> $languages + */ + private function createVersionInfo( + ContentInfo $contentInfo, + iterable $languages + ): VersionInfo { + $versionInfo = $this->createMock(VersionInfo::class); + $versionInfo + ->method('getContentInfo') + ->willReturn($contentInfo); + $versionInfo + ->method('getLanguages') + ->willReturn($languages); + + return $versionInfo; + } + + private function createLocation(): Location + { + return $this->createMock(Location::class); + } + + private function createLanguage( + int $id, + bool $enabled, + string $languageCode, + string $name + ): Language { + return new Language( + [ + 'id' => $id, + 'enabled' => $enabled, + 'languageCode' => $languageCode, + 'name' => $name, + ] + ); + } + + /** + * @return array{ + * languageCode: string, + * name: string, + * hasAccess: bool, + * } + */ + private function getLanguageAccessData( + bool $hasAccess, + Language $language + ): array { + return [ + 'languageCode' => $language->getLanguageCode(), + 'name' => $language->getName(), + 'hasAccess' => $hasAccess, + ]; + } + + /** + * @param array $limitationValues + */ + private function createLanguageLimitation(array $limitationValues): Limitation\LanguageLimitation + { + return new Limitation\LanguageLimitation( + [ + 'limitationValues' => $limitationValues, + ] + ); + } + + private function mockPermissionResolverLookupLimitations( + ContentInfo $contentInfo, + Location $location, + LookupLimitationResult $lookupLimitationResult + ): void { + $languageCodes = [ + 'eng-GB', + 'ger-DE', + ]; + $targets = [ + (new VersionBuilder())->translateToAnyLanguageOf($languageCodes)->build(), + $location, + ]; + + $this->permissionResolver + ->method('lookupLimitations') + ->with( + 'content', + 'edit', + $contentInfo, + $targets, + [Limitation::LANGUAGE], + ) + ->willReturn($lookupLimitationResult); + } +} diff --git a/tests/lib/Permission/PermissionCheckerTest.php b/tests/lib/Permission/PermissionCheckerTest.php index caba5cec03..c518d4848d 100644 --- a/tests/lib/Permission/PermissionCheckerTest.php +++ b/tests/lib/Permission/PermissionCheckerTest.php @@ -8,11 +8,8 @@ namespace Ibexa\Tests\AdminUi\Permission; +use Ibexa\AdminUi\Permission\LimitationResolverInterface; use Ibexa\AdminUi\Permission\PermissionChecker; -use Ibexa\Contracts\Core\Repository\ContentService; -use Ibexa\Contracts\Core\Repository\ContentTypeService; -use Ibexa\Contracts\Core\Repository\LanguageService; -use Ibexa\Contracts\Core\Repository\LocationService; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\Repository\Values\Content; @@ -27,23 +24,14 @@ class PermissionCheckerTest extends TestCase { private const USER_ID = 14; - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ + /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver&\PHPUnit\Framework\MockObject\MockObject */ private $permissionResolver; - /** @var \Ibexa\Contracts\Core\Repository\UserService */ + /** @var \Ibexa\Contracts\Core\Repository\UserService&\PHPUnit\Framework\MockObject\MockObject */ private $userService; - /** @var \Ibexa\Contracts\Core\Repository\LocationService */ - private $locationService; - - /** @var \Ibexa\Contracts\Core\Repository\ContentService */ - private $contentService; - - /** @var \Ibexa\Contracts\Core\Repository\ContentTypeService */ - private $contentTypeService; - - /** @var \Ibexa\Contracts\Core\Repository\LanguageService */ - private $languageService; + /** @var \Ibexa\AdminUi\Permission\LimitationResolverInterface&\PHPUnit\Framework\MockObject\MockObject */ + private LimitationResolverInterface $permissionLimitationResolver; /** @var \Ibexa\AdminUi\Permission\PermissionChecker */ private $permissionChecker; @@ -55,19 +43,13 @@ public function setUp(): void ->method('getCurrentUserReference') ->willReturn($this->generateUser(self::USER_ID)); + $this->permissionLimitationResolver = $this->createMock(LimitationResolverInterface::class); $this->userService = $this->createMock(UserService::class); - $this->locationService = $this->createMock(LocationService::class); - $this->contentService = $this->createMock(ContentService::class); - $this->contentTypeService = $this->createMock(ContentTypeService::class); - $this->languageService = $this->createMock(LanguageService::class); $this->permissionChecker = new PermissionChecker( $this->permissionResolver, + $this->permissionLimitationResolver, $this->userService, - $this->locationService, - $this->contentService, - $this->contentTypeService, - $this->languageService ); } @@ -212,4 +194,4 @@ private function generateUser(int $id): User } } -class_alias(PermissionCheckerTest::class, 'EzSystems\EzPlatformAdminUi\Tests\Permission\PermissionCheckerTest'); +class_alias(PermissionCheckerTest::class, 'EzSystems\EzPlatformAdminUi\Tests\Permission\PermissionCheckerTest'); \ No newline at end of file