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
155 changes: 155 additions & 0 deletions tests/integration/Core/Repository/ContentService/UpdateContentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?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());
self::assertCount(1, (array)$relations);
reithor marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @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
*/
protected function createUser(string $login, string $firstName, string $lastName, UserGroup $userGroup = null): User
reithor marked this conversation as resolved.
Show resolved Hide resolved
{
$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