Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
EZP-29850: As a Developer I want API to assign a Section to a subtree (
  • Loading branch information
adamwojs authored and Łukasz Serwatka committed Jan 28, 2019
1 parent 9a380fa commit b40ee9a
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 33 deletions.
3 changes: 2 additions & 1 deletion eZ/Publish/API/Repository/PermissionCriterionResolver.php
Expand Up @@ -21,8 +21,9 @@ interface PermissionCriterionResolver
*
* @param string $module
* @param string $function
* @param array|null $targets
*
* @return bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
*/
public function getPermissionsCriterion($module, $function);
public function getPermissionsCriterion($module, $function, ?array $targets = null);
}
10 changes: 10 additions & 0 deletions eZ/Publish/API/Repository/SectionService.php
Expand Up @@ -8,6 +8,7 @@
*/
namespace eZ\Publish\API\Repository;

use eZ\Publish\API\Repository\Values\Content\Location;
use eZ\Publish\API\Repository\Values\Content\SectionCreateStruct;
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
use eZ\Publish\API\Repository\Values\Content\Section;
Expand Down Expand Up @@ -111,6 +112,15 @@ public function isSectionUsed(Section $section);
*/
public function assignSection(ContentInfo $contentInfo, Section $section);

/**
* Assigns the subtree to the given section
* this method overrides the current assigned section.
*
* @param \eZ\Publish\API\Repository\Values\Content\Location $location
* @param \eZ\Publish\API\Repository\Values\Content\Section $section
*/
public function assignSectionToSubtree(Location $location, Section $section): void;

/**
* Deletes $section from content repository.
*
Expand Down
52 changes: 52 additions & 0 deletions eZ/Publish/API/Repository/Tests/SectionServiceTest.php
Expand Up @@ -588,6 +588,58 @@ public function testAssignSection()
);
}

/**
* Test for the assignSectionToSubtree() method.
*
* @see \eZ\Publish\API\Repository\SectionService::assignSectionToSubtree()
* @depends eZ\Publish\API\Repository\Tests\SectionServiceTest::testCreateSection
*/
public function testAssignSectionToSubtree()
{
$repository = $this->getRepository();
$sectionService = $repository->getSectionService();

$standardSectionId = $this->generateId('section', 1);
$mediaSectionId = $this->generateId('section', 3);

$beforeStandardCount = $sectionService->countAssignedContents(
$sectionService->loadSection($standardSectionId)
);

$beforeMediaCount = $sectionService->countAssignedContents(
$sectionService->loadSection($mediaSectionId)
);

// RemoteId of the "Media" page of an eZ Publish demo installation
$mediaRemoteId = '75c715a51699d2d309a924eca6a95145';

/* BEGIN: Use Case */
$locationService = $repository->getLocationService();

// Load a location instance
$location = $locationService->loadLocationByRemoteId($mediaRemoteId);

// Load the "Standard" section
$section = $sectionService->loadSection($standardSectionId);

// Assign Section to ContentInfo
$sectionService->assignSectionToSubtree($location, $section);

/* END: Use Case */
$this->assertEquals(
$beforeStandardCount + 4,
$sectionService->countAssignedContents(
$sectionService->loadSection($standardSectionId)
)
);
$this->assertEquals(
$beforeMediaCount - 4,
$sectionService->countAssignedContents(
$sectionService->loadSection($mediaSectionId)
)
);
}

/**
* Test for the countAssignedContents() method.
*
Expand Down
60 changes: 47 additions & 13 deletions eZ/Publish/Core/Limitation/NewSectionLimitationType.php
Expand Up @@ -9,6 +9,9 @@
namespace eZ\Publish\Core\Limitation;

use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchAll;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchNone;
use eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface;
use eZ\Publish\API\Repository\Values\ValueObject;
use eZ\Publish\API\Repository\Values\User\UserReference as APIUserReference;
use eZ\Publish\API\Repository\Values\Content\Content;
Expand All @@ -26,7 +29,7 @@
/**
* NewSectionLimitation is a Content Limitation used on 'section' 'assign' function.
*/
class NewSectionLimitationType extends AbstractPersistenceLimitationType implements SPILimitationTypeInterface
class NewSectionLimitationType extends AbstractPersistenceLimitationType implements SPILimitationTypeInterface, TargetOnlyLimitationType
{
/**
* Accepts a Limitation value and checks for structural validity.
Expand Down Expand Up @@ -116,7 +119,7 @@ public function evaluate(APILimitationValue $value, APIUserReference $currentUse
}

if (!$object instanceof ContentInfo && !$object instanceof Content && !$object instanceof VersionInfo) {
throw new InvalidArgumentException('$object', 'Must be of type: Content, VersionInfo or ContentInfo');
throw new InvalidArgumentException('$object', 'Must be of type: Content, VersionInfo, ContentInfo');
}

if (empty($targets)) {
Expand All @@ -127,17 +130,7 @@ public function evaluate(APILimitationValue $value, APIUserReference $currentUse
return false;
}

foreach ($targets as $target) {
if (!$target instanceof Section && !$target instanceof SPISection) {
throw new InvalidArgumentException('$targets', 'Must contain objects of type: Section');
}

if (!in_array($target->id, $value->limitationValues)) {
return false;
}
}

return true;
return $this->doEvaluate($value, $targets);
}

/**
Expand Down Expand Up @@ -165,4 +158,45 @@ public function valueSchema()
{
throw new \eZ\Publish\API\Repository\Exceptions\NotImplementedException(__METHOD__);
}

/**
* {@inheritdoc}
*/
public function getCriterionByTarget(APILimitationValue $value, APIUserReference $currentUser, ?array $targets): CriterionInterface
{
if (empty($targets)) {
throw new InvalidArgumentException('$targets', 'Must contain objects of type: Section');
}

if ($this->doEvaluate($value, $targets)) {
return new MatchAll();
} else {
return new MatchNone();
}
}

/**
* Returns true if given limitation value allows all given sections.
*
* @param \eZ\Publish\API\Repository\Values\User\Limitation $value
* @param array|null $targets
*
* @return bool
*
* @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
*/
private function doEvaluate(APILimitationValue $value, array $targets): bool
{
foreach ($targets as $target) {
if (!$target instanceof Section && !$target instanceof SPISection) {
throw new InvalidArgumentException('$targets', 'Must contain objects of type: Section');
}

if (!in_array($target->id, $value->limitationValues)) {
return false;
}
}

return true;
}
}
35 changes: 35 additions & 0 deletions eZ/Publish/Core/Limitation/TargetOnlyLimitationType.php
@@ -0,0 +1,35 @@
<?php

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

namespace eZ\Publish\Core\Limitation;

use eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface;
use eZ\Publish\API\Repository\Values\User\Limitation as APILimitationValue;
use eZ\Publish\API\Repository\Values\User\UserReference as APIUserReference;
use eZ\Publish\SPI\Limitation\Type as LimitationTypeInterface;

/**
* Limitation type which doesn't take $object into consideration while evaluation.
*
* @see \eZ\Publish\Core\Repository\Permission\PermissionCriterionResolver::getPermissionsCriterion
*/
interface TargetOnlyLimitationType extends LimitationTypeInterface
{
/**
* Returns criterion based on given $target for use in find() query.
*
* @param \eZ\Publish\API\Repository\Values\User\Limitation $value
* @param \eZ\Publish\API\Repository\Values\User\UserReference $currentUser
* @param array|null $targets
*
* @return \eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface
*
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
public function getCriterionByTarget(APILimitationValue $value, APIUserReference $currentUser, ?array $targets): CriterionInterface;
}
Expand Up @@ -104,12 +104,12 @@ public function canUser($module, $function, ValueObject $object, array $targets
return $this->permissionResolver->canUser($module, $function, $object, $targets);
}

public function getPermissionsCriterion($module = 'content', $function = 'read')
public function getPermissionsCriterion($module = 'content', $function = 'read', ?array $targets = null)
{
// We only cache content/read lookup as those are the once frequently done, and it's only one we can safely
// do that won't harm the system if it becomes stale (but user might experience permissions exceptions if it do)
if ($module !== 'content' || $function !== 'read' || $this->sudoNestingLevel > 0) {
return $this->permissionCriterionResolver->getPermissionsCriterion($module, $function);
return $this->permissionCriterionResolver->getPermissionsCriterion($module, $function, $targets);
}

if ($this->permissionCriterion !== null) {
Expand All @@ -120,7 +120,7 @@ public function getPermissionsCriterion($module = 'content', $function = 'read')
}

$this->permissionCriterionTs = time();
$this->permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion($module, $function);
$this->permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion($module, $function, $targets);

return $this->permissionCriterion;
}
Expand Down
Expand Up @@ -9,9 +9,12 @@
use eZ\Publish\API\Repository\PermissionCriterionResolver as APIPermissionCriterionResolver;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalOr;
use eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface;
use eZ\Publish\API\Repository\Values\User\Limitation;
use eZ\Publish\API\Repository\PermissionResolver as PermissionResolverInterface;
use eZ\Publish\API\Repository\Values\User\UserReference;
use eZ\Publish\Core\Repository\Helper\LimitationService;
use eZ\Publish\Core\Limitation\TargetOnlyLimitationType;
use RuntimeException;

/**
Expand Down Expand Up @@ -44,19 +47,18 @@ public function __construct(
}

/**
* Get content-read Permission criteria if needed and return false if no access at all.
* Get permission criteria if needed and return false if no access at all.
*
* @uses \eZ\Publish\API\Repository\PermissionResolver::getCurrentUserReference()
* @uses \eZ\Publish\API\Repository\PermissionResolver::hasAccess()
*
* @throws \RuntimeException If empty array of limitations are provided from hasAccess()
*
* @param string $module
* @param string $function
* @param array $targets
*
* @return bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
*/
public function getPermissionsCriterion($module = 'content', $function = 'read')
public function getPermissionsCriterion($module = 'content', $function = 'read', ?array $targets = null)
{
$permissionSets = $this->permissionResolver->hasAccess($module, $function);
if (is_bool($permissionSets)) {
Expand Down Expand Up @@ -92,8 +94,7 @@ public function getPermissionsCriterion($module = 'content', $function = 'read')

$limitationsAndCriteria = [];
foreach ($limitations as $limitation) {
$type = $this->limitationService->getLimitationType($limitation->getIdentifier());
$limitationsAndCriteria[] = $type->getCriterion($limitation, $currentUserRef);
$limitationsAndCriteria[] = $this->getCriterionForLimitation($limitation, $currentUserRef, $targets);
}

$policyOrCriteria[] = isset($limitationsAndCriteria[1]) ?
Expand All @@ -108,16 +109,18 @@ public function getPermissionsCriterion($module = 'content', $function = 'read')
*/
if ($permissionSet['limitation'] instanceof Limitation) {
// We need to match both the limitation AND *one* of the policies, aka; roleLimit AND policies(OR)
$type = $this->limitationService->getLimitationType($permissionSet['limitation']->getIdentifier());
if (!empty($policyOrCriteria)) {
$criterion = $this->getCriterionForLimitation($permissionSet['limitation'], $currentUserRef, $targets);
$roleAssignmentOrCriteria[] = new LogicalAnd(
[
$type->getCriterion($permissionSet['limitation'], $currentUserRef),
$criterion,
isset($policyOrCriteria[1]) ? new LogicalOr($policyOrCriteria) : $policyOrCriteria[0],
]
);
} else {
$roleAssignmentOrCriteria[] = $type->getCriterion($permissionSet['limitation'], $currentUserRef);
$roleAssignmentOrCriteria[] = $this->getCriterionForLimitation(
$permissionSet['limitation'], $currentUserRef, $targets
);
}
} elseif (!empty($policyOrCriteria)) {
// Otherwise merge $policyOrCriteria into $roleAssignmentOrCriteria
Expand All @@ -136,4 +139,21 @@ public function getPermissionsCriterion($module = 'content', $function = 'read')
new LogicalOr($roleAssignmentOrCriteria) :
$roleAssignmentOrCriteria[0];
}

/**
* @param \eZ\Publish\API\Repository\Values\User\Limitation $limitation
* @param \eZ\Publish\API\Repository\Values\User\UserReference $currentUserRef
* @param array|null $targets
*
* @return \eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface|\eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalOperator
*/
private function getCriterionForLimitation(Limitation $limitation, UserReference $currentUserRef, ?array $targets): CriterionInterface
{
$type = $this->limitationService->getLimitationType($limitation->getIdentifier());
if ($type instanceof TargetOnlyLimitationType) {
return $type->getCriterionByTarget($limitation, $currentUserRef, $targets);
}

return $type->getCriterion($limitation, $currentUserRef);
}
}
2 changes: 2 additions & 0 deletions eZ/Publish/Core/Repository/Repository.php
Expand Up @@ -605,6 +605,8 @@ public function getSectionService()
$this->sectionService = new SectionService(
$this,
$this->persistenceHandler->sectionHandler(),
$this->persistenceHandler->locationHandler(),
$this->getPermissionCriterionResolver(),
$this->serviceSettings['section']
);

Expand Down

0 comments on commit b40ee9a

Please sign in to comment.