diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d39d471e17..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 diff --git a/src/lib/Permission/LimitationResolver.php b/src/lib/Permission/LimitationResolver.php index e4e7a6f457..a8e651f001 100644 --- a/src/lib/Permission/LimitationResolver.php +++ b/src/lib/Permission/LimitationResolver.php @@ -110,12 +110,11 @@ public function getLanguageLimitations( $limitationLanguageCodes = $this->lookupLimitationsTransformer->getFlattenedLimitationsValues($lookupLimitations); $languageLimitations = []; - foreach ($languages as $language) { $languageLimitations[] = [ 'languageCode' => $language->getLanguageCode(), 'name' => $language->getName(), - 'hasAccess' => $this->hasAccessToLanguage($language, $limitationLanguageCodes), + 'hasAccess' => $lookupLimitations->hasAccess && $this->hasAccessToLanguage($language, $limitationLanguageCodes), ]; } diff --git a/tests/lib/Permission/LimitationResolverTest.php b/tests/lib/Permission/LimitationResolverTest.php new file mode 100644 index 0000000000..fad6bc151e --- /dev/null +++ b/tests/lib/Permission/LimitationResolverTest.php @@ -0,0 +1,315 @@ +contentService = $this->createMock(ContentService::class); + $this->contentTypeService = $this->createMock(ContentTypeService::class); + $this->languageService = $this->createMock(LanguageService::class); + $this->locationService = $this->createMock(LocationService::class); + $this->lookupLimitationsTransformer = new LookupLimitationsTransformer(); + $this->permissionResolver = $this->createMock(PermissionResolver::class); + + $this->limitationResolver = new LimitationResolver( + $this->contentService, + $this->contentTypeService, + $this->languageService, + $this->locationService, + $this->lookupLimitationsTransformer, + $this->permissionResolver + ); + } + + /** + * @dataProvider provideDataForTestGetLanguageLimitations + * + * @param array $expected + */ + public function testGetLanguageLimitations( + array $expected, + VersionInfo $versionInfo, + Location $location, + LookupLimitationResult $lookupLimitationResult + ): void { + $this->mockPermissionResolverLookupLimitations( + [ + 'eng-GB', + 'ger-DE', + ], + $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 'Has access to all enabled languages' => [ + [ + $this->getLanguageAccessData(true, $english), + $this->getLanguageAccessData(true, $german), + $this->getLanguageAccessData(false, $french), + ], + $versionInfo, + $location, + new LookupLimitationResult(true), + ]; + + yield 'Has 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 'Has 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 'Has 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, + ] + ); + } + + /** + * @param array $languageCodes + */ + private function mockPermissionResolverLookupLimitations( + array $languageCodes, + ContentInfo $contentInfo, + Location $location, + LookupLimitationResult $lookupLimitationResult + ): void { + $this->permissionResolver + ->method('lookupLimitations') + ->with( + 'content', + 'edit', + $contentInfo, + [ + (new VersionBuilder())->translateToAnyLanguageOf($languageCodes)->build(), + $location, + ], + [Limitation::LANGUAGE], + ) + ->willReturn($lookupLimitationResult); + } +}