diff --git a/src/lib/Repository/ContentService.php b/src/lib/Repository/ContentService.php index f657acdbef..2f1c48b250 100644 --- a/src/lib/Repository/ContentService.php +++ b/src/lib/Repository/ContentService.php @@ -1423,7 +1423,7 @@ protected function internalUpdateContent( )->id, ] ); - $existingRelations = $this->internalLoadRelations($versionInfo); + $existingRelations = $this->repository->sudo(fn (): array => $this->internalLoadRelations($versionInfo)); $this->repository->beginTransaction(); try { diff --git a/tests/integration/Core/Repository/ContentService/UpdateContentTest.php b/tests/integration/Core/Repository/ContentService/UpdateContentTest.php new file mode 100644 index 0000000000..0dd3f900f6 --- /dev/null +++ b/tests/integration/Core/Repository/ContentService/UpdateContentTest.php @@ -0,0 +1,158 @@ +addRelationFieldToFolderContentType(); + + $privateSection = $this->createPrivateSection(); + + $folderPrivate = $this->createFolder(['eng-GB' => 'Private Folder'], 2); + $sectionService->assignSection($folderPrivate->getContentInfo(), $privateSection); + + // Create folder with relation to 'Private Folder' + $folder = $this->createFolderWithRelations([$folderPrivate->getId()]); + + $userWithRoleLimitation = $this->createUserWithNoAccessToPrivateSection(); + + // Create & publish new $folder version as $editor + $permissionResolver->setCurrentUserReference($userWithRoleLimitation); + $folder = $this->publishVersionWithoutChanges($folder->getContentInfo()); + + // Read relations & check if count($relations) is unchanged + self::setAdministratorUser(); + $relations = $contentService->loadRelations($folder->getVersionInfo()); + if ($relations instanceof \Traversable) { + $relations = iterator_to_array($relations); + } + self::assertCount(1, $relations); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + private function addRelationFieldToFolderContentType(): void + { + $contentTypeService = self::getContentTypeService(); + $folderType = $contentTypeService->loadContentTypeByIdentifier('folder'); + $folderTypeDraft = $contentTypeService->createContentTypeDraft($folderType); + + $relationsFieldCreateStruct = $contentTypeService->newFieldDefinitionCreateStruct( + 'relations', + 'ezobjectrelationlist' + ); + $relationsFieldCreateStruct->names = ['eng-GB' => 'Relations']; + $contentTypeService->addFieldDefinition($folderTypeDraft, $relationsFieldCreateStruct); + $contentTypeService->publishContentTypeDraft($folderTypeDraft); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + private function createPrivateSection(): Section + { + $sectionService = self::getSectionService(); + + $sectionCreateStruct = $sectionService->newSectionCreateStruct(); + $sectionCreateStruct->identifier = 'private'; + $sectionCreateStruct->name = 'Private Section'; + + return $sectionService->createSection($sectionCreateStruct); + } + + /** + * @param int[] $relationListTarget + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + private function createFolderWithRelations(array $relationListTarget): Content + { + $contentService = self::getContentService(); + + $folder = $this->createFolder(['eng-GB' => 'Folder with private relation'], 2); + $folderDraft = $contentService->createContentDraft($folder->getContentInfo()); + $folderUpdateStruct = $contentService->newContentUpdateStruct(); + $folderUpdateStruct->setField('relations', $relationListTarget); + + $folder = $contentService->updateContent($folderDraft->getVersionInfo(), $folderUpdateStruct); + + return $contentService->publishVersion($folder->getVersionInfo()); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\LimitationValidationException + */ + private function assignToUserRoleWithStandardSectionLimitation(User $user): void + { + $sectionService = self::getSectionService(); + $roleService = self::getRoleService(); + + $roleCreateStruct = $roleService->newRoleCreateStruct('limited_access'); + $roleCreateStruct->addPolicy($roleService->newPolicyCreateStruct('*', '*')); + $role = $roleService->createRole($roleCreateStruct); + $roleService->publishRoleDraft($role); + + // limit access to standard section only on the role assignment level + $standardSection = $sectionService->loadSectionByIdentifier('standard'); + $roleService->assignRoleToUser( + $role, + $user, + new SectionLimitation(['limitationValues' => [$standardSection->id]]) + ); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + private function createUserWithNoAccessToPrivateSection(): User + { + $user = $this->createUser('test.editor', 'Editor', 'Test'); + $this->assignToUserRoleWithStandardSectionLimitation($user); + + return $user; + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + private function publishVersionWithoutChanges(ContentInfo $contentInfo): Content + { + $contentService = self::getContentService(); + + $folderDraft = $contentService->createContentDraft($contentInfo); + $folderUpdateStruct = $contentService->newContentUpdateStruct(); + $folder = $contentService->updateContent($folderDraft->getVersionInfo(), $folderUpdateStruct); + + return $contentService->publishVersion($folder->getVersionInfo()); + } +} diff --git a/tests/integration/Core/RepositoryTestCase.php b/tests/integration/Core/RepositoryTestCase.php index 6a4955b112..97de4dc0d1 100644 --- a/tests/integration/Core/RepositoryTestCase.php +++ b/tests/integration/Core/RepositoryTestCase.php @@ -9,6 +9,8 @@ namespace Ibexa\Tests\Integration\Core; use Ibexa\Contracts\Core\Repository\Values\Content\Content; +use Ibexa\Contracts\Core\Repository\Values\User\User; +use Ibexa\Contracts\Core\Repository\Values\User\UserGroup; use Ibexa\Contracts\Core\Test\IbexaKernelTestCase; use InvalidArgumentException; @@ -17,6 +19,7 @@ abstract class RepositoryTestCase extends IbexaKernelTestCase public const CONTENT_TREE_ROOT_ID = 2; private const CONTENT_TYPE_FOLDER_IDENTIFIER = 'folder'; + private const MAIN_USER_GROUP_REMOTE_ID = 'f5c88a2209584891056f987fd965b0ba'; protected function setUp(): void { @@ -41,6 +44,37 @@ public function createFolder(array $names, int $parentLocationId = self::CONTENT return $contentService->publishVersion($draft->getVersionInfo()); } + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + final protected function createUser(string $login, string $firstName, string $lastName, UserGroup $userGroup = null): User + { + $userService = self::getUserService(); + + if (null === $userGroup) { + $userGroup = $userService->loadUserGroupByRemoteId(self::MAIN_USER_GROUP_REMOTE_ID); + } + + $userCreateStruct = $userService->newUserCreateStruct( + $login, + "$login@mail.invalid", + 'secret', + 'eng-US' + ); + $userCreateStruct->enabled = true; + + // Set some fields required by the user ContentType + $userCreateStruct->setField('first_name', $firstName); + $userCreateStruct->setField('last_name', $lastName); + + // Create a new user instance. + return $userService->createUser($userCreateStruct, [$userGroup]); + } + /** * @param array $names * diff --git a/tests/lib/Repository/Service/Mock/ContentTest.php b/tests/lib/Repository/Service/Mock/ContentTest.php index bbc90db299..bd6071f30b 100644 --- a/tests/lib/Repository/Service/Mock/ContentTest.php +++ b/tests/lib/Repository/Service/Mock/ContentTest.php @@ -3331,7 +3331,7 @@ protected function assertForTestUpdateContentNonRedundantFieldSet( ->expects(self::once()) ->method('getCurrentUserReference') ->willReturn(new UserReference(169)); - $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'internalLoadRelations'], $permissionResolverMock); + $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById'], $permissionResolverMock); $permissionResolverMock = $this->getPermissionResolverMock(); /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */ $contentHandlerMock = $this->getPersistenceMock()->contentHandler(); @@ -3484,11 +3484,8 @@ static function (SPIValue $value) use ($emptyValue) { )->will(self::returnValue([])); $existingRelations = ['RELATIONS!!!']; - $mockedService - ->method('internalLoadRelations') - ->with($content->versionInfo) - ->will(self::returnValue($existingRelations)); - $relationProcessorMock->expects(self::any()) + $repositoryMock->method('sudo')->willReturn($existingRelations); + $relationProcessorMock->expects($this->any()) ->method('processFieldRelations') ->with( self::isType('array'),