Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-8426: Fixed duplicate relations #390

Merged
merged 10 commits into from
Jul 15, 2024
2 changes: 1 addition & 1 deletion src/lib/Repository/ContentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ protected function internalUpdateContent(
)->id,
]
);
$existingRelations = $this->internalLoadRelations($versionInfo);
$existingRelations = $this->repository->sudo(fn (): array => $this->internalLoadRelations($versionInfo));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note: it's done like that because there are some architecture issues that are not easy to fix, see #390 (review)


$this->repository->beginTransaction();
try {
Expand Down
158 changes: 158 additions & 0 deletions tests/integration/Core/Repository/ContentService/UpdateContentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\Integration\Core\Repository\ContentService;

use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\Values\Content\Section;
use Ibexa\Contracts\Core\Repository\Values\User\Limitation\SectionLimitation;
use Ibexa\Contracts\Core\Repository\Values\User\User;
use Ibexa\Tests\Integration\Core\RepositoryTestCase;

/**
* @covers \Ibexa\Contracts\Core\Repository\ContentService
*/
final class UpdateContentTest extends RepositoryTestCase
{
/**
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception
*/
public function testUpdateContentHavingPrivateRelation(): void
{
$sectionService = self::getSectionService();
$contentService = self::getContentService();
$permissionResolver = self::getPermissionResolver();

$this->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());
}
}
34 changes: 34 additions & 0 deletions tests/integration/Core/RepositoryTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
{
Expand All @@ -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,
"[email protected]",
'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<string, string> $names
*
Expand Down
7 changes: 2 additions & 5 deletions tests/lib/Repository/Service/Mock/ContentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3317,7 +3317,7 @@ protected function assertForTestUpdateContentNonRedundantFieldSet(
->expects($this->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();
Expand Down Expand Up @@ -3470,10 +3470,7 @@ static function (SPIValue $value) use ($emptyValue) {
)->will($this->returnValue([]));

$existingRelations = ['RELATIONS!!!'];
$mockedService
->method('internalLoadRelations')
->with($content->versionInfo)
->will($this->returnValue($existingRelations));
$repositoryMock->method('sudo')->willReturn($existingRelations);
$relationProcessorMock->expects($this->any())
->method('processFieldRelations')
->with(
Expand Down
Loading