Skip to content

Commit

Permalink
Implement REST for NodeExtendedInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
tischsoic committed Mar 14, 2024
1 parent c003ec1 commit ab5a8ac
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 4 deletions.
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ parameters:
count: 1
path: src/bundle/Controller/BulkOperation/BulkOperationController.php

-
message: "#^Access to protected property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\User\\\\LookupLimitationResult\\:\\:\\$hasAccess\\.$#"
count: 4
path: src/bundle/Controller/Content/ContentTreeController.php

-
message: "#^Parameter \\#1 \\$locationId of method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\LocationService\\:\\:loadLocation\\(\\) expects int, int\\|null given\\.$#"
count: 1
Expand Down
96 changes: 92 additions & 4 deletions src/bundle/Controller/Content/ContentTreeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,45 @@

namespace Ibexa\Bundle\AdminUi\Controller\Content;

use Ibexa\AdminUi\Permission\LookupLimitationsTransformer;
use Ibexa\AdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode;
use Ibexa\AdminUi\REST\Value\ContentTree\Node;
use Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo;
use Ibexa\AdminUi\REST\Value\ContentTree\Root;
use Ibexa\AdminUi\UI\Module\ContentTree\NodeFactory;
use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface;
use Ibexa\Contracts\Core\Repository\LocationService;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\User\Limitation;
use Ibexa\Rest\Message;
use Ibexa\Rest\Server\Controller as RestController;
use Symfony\Component\HttpFoundation\Request;

/**
* @phpstan-import-type PermissionRestrictions from \Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo
*/
class ContentTreeController extends RestController
{
/** @var \Ibexa\Contracts\Core\Repository\LocationService */
private $locationService;

private PermissionCheckerInterface $permissionChecker;

private LookupLimitationsTransformer $lookupLimitationsTransformer;

/** @var \Ibexa\AdminUi\UI\Module\ContentTree\NodeFactory */
private $contentTreeNodeFactory;

/**
* @param \Ibexa\Contracts\Core\Repository\LocationService $locationService
* @param \Ibexa\AdminUi\UI\Module\ContentTree\NodeFactory $contentTreeNodeFactory
*/
public function __construct(
LocationService $locationService,
PermissionCheckerInterface $permissionChecker,
LookupLimitationsTransformer $lookupLimitationsTransformer,
NodeFactory $contentTreeNodeFactory
) {
$this->locationService = $locationService;
$this->permissionChecker = $permissionChecker;
$this->lookupLimitationsTransformer = $lookupLimitationsTransformer;
$this->contentTreeNodeFactory = $contentTreeNodeFactory;
}

Expand Down Expand Up @@ -110,6 +122,82 @@ public function loadSubtreeAction(Request $request): Root

return new Root($elements);
}

public function loadNodeExtendedInfoAction(Request $request): NodeExtendedInfo
{
/** @var \Ibexa\AdminUi\REST\Value\ContentTree\LoadNodeExtendedInfoRequest $loadSubtreeRequest */
$loadSubtreeRequest = $this->inputDispatcher->parse(
new Message(
['Content-Type' => $request->headers->get('Content-Type')],
$request->getContent()
)
);

$locationId = $loadSubtreeRequest->locationId;
$location = $this->locationService->loadLocation($locationId);
$locationPermissionRestrictions = $this->getLocationPermissionRestrictions($location);

return new NodeExtendedInfo($locationPermissionRestrictions);
}

/**
* @return PermissionRestrictions
*
* @throws \Ibexa\AdminUi\Exception\InvalidArgumentException
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
*/
public function getLocationPermissionRestrictions(Location $location): array
{
$lookupCreateLimitationsResult = $this->permissionChecker->getContentCreateLimitations($location);
$lookupUpdateLimitationsResult = $this->permissionChecker->getContentUpdateLimitations($location);
$lookupDeleteLimitationsResult = $this->permissionChecker->getContentDeleteLimitations($location);
$lookupHideLimitationsResult = $this->permissionChecker->getContentHideLimitations($location);

$createLimitationsValues = $this->lookupLimitationsTransformer->getGroupedLimitationValues(
$lookupCreateLimitationsResult,
[Limitation::CONTENTTYPE, Limitation::LANGUAGE]
);

$updateLimitationsValues = $this->lookupLimitationsTransformer->getGroupedLimitationValues(
$lookupUpdateLimitationsResult,
[Limitation::CONTENTTYPE, Limitation::LANGUAGE]
);

$deleteLimitationsValues = $this->lookupLimitationsTransformer->getGroupedLimitationValues(
$lookupDeleteLimitationsResult,
[Limitation::CONTENTTYPE, Limitation::LANGUAGE]
);

$hideLimitationsValues = $this->lookupLimitationsTransformer->getGroupedLimitationValues(
$lookupHideLimitationsResult,
[Limitation::CONTENTTYPE, Limitation::LANGUAGE]
);

return [
'create' => [
'hasAccess' => $lookupCreateLimitationsResult->hasAccess,
'restrictedContentTypeIds' => $createLimitationsValues[Limitation::CONTENTTYPE],
'restrictedLanguageCodes' => $createLimitationsValues[Limitation::LANGUAGE],
],
'edit' => [
'hasAccess' => $lookupUpdateLimitationsResult->hasAccess,
'restrictedContentTypeIds' => $updateLimitationsValues[Limitation::CONTENTTYPE],
'restrictedLanguageCodes' => $updateLimitationsValues[Limitation::LANGUAGE],
],
'delete' => [
'hasAccess' => $lookupDeleteLimitationsResult->hasAccess,
'restrictedContentTypeIds' => $deleteLimitationsValues[Limitation::CONTENTTYPE],
'restrictedLanguageCodes' => $deleteLimitationsValues[Limitation::LANGUAGE],
],
'hide' => [
'hasAccess' => $lookupHideLimitationsResult->hasAccess,
'restrictedContentTypeIds' => $hideLimitationsValues[Limitation::CONTENTTYPE],
'restrictedLanguageCodes' => $hideLimitationsValues[Limitation::LANGUAGE],
],
];
}
}

class_alias(ContentTreeController::class, 'EzSystems\EzPlatformAdminUiBundle\Controller\Content\ContentTreeController');
9 changes: 9 additions & 0 deletions src/bundle/Resources/config/routing_rest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ ibexa.rest.location.tree.load_subtree:
defaults:
_controller: 'Ibexa\Bundle\AdminUi\Controller\Content\ContentTreeController::loadSubtreeAction'

ibexa.rest.location.tree.load_node_extended_info:
# @todo change name to content tree
path: /location/tree/load-node-extended-info
methods: ['POST']
options:
expose: true
defaults:
_controller: 'Ibexa\Bundle\AdminUi\Controller\Content\ContentTreeController::loadNodeExtendedInfoAction'

#
# Content type create/edit form
#
Expand Down
10 changes: 10 additions & 0 deletions src/bundle/Resources/config/services/rest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ services:
tags:
- { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.ContentTreeLoadSubtreeRequest }

Ibexa\AdminUi\REST\Input\Parser\ContentTree\LoadExtendedNodeInfoRequest:
parent: Ibexa\Rest\Server\Common\Parser
tags:
- { name: ibexa.rest.input.parser, mediaType: application/vnd.ibexa.api.ContentTreeLoadExtendedNodeInfoRequest }

Ibexa\AdminUi\REST\Output\ValueObjectVisitor\ContentTree\Node:
parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
tags:
Expand All @@ -38,6 +43,11 @@ services:
tags:
- { name: ibexa.rest.output.value_object.visitor, type: Ibexa\AdminUi\REST\Value\ContentTree\Root }

Ibexa\AdminUi\REST\Output\ValueObjectVisitor\ContentTree\NodeExtendedInfo:
parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor
tags:
- { name: ibexa.rest.output.value_object.visitor, type: Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo }

#
# Content type create/edit form
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?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\AdminUi\REST\Input\Parser\ContentTree;

use Ibexa\AdminUi\REST\Value\ContentTree\LoadNodeExtendedInfoRequest as LoadNodeExtendedInfoRequestValue;
use Ibexa\Contracts\Rest\Exceptions;
use Ibexa\Contracts\Rest\Input\ParsingDispatcher;
use Ibexa\Rest\Input\BaseParser;

class LoadExtendedNodeInfoRequest extends BaseParser
{
/**
* {@inheritdoc}
*/
public function parse(array $data, ParsingDispatcher $parsingDispatcher): LoadNodeExtendedInfoRequestValue

Check failure on line 21 in src/lib/REST/Input/Parser/ContentTree/LoadExtendedNodeInfoRequest.php

View workflow job for this annotation

GitHub Actions / Tests (7.4)

Method Ibexa\AdminUi\REST\Input\Parser\ContentTree\LoadExtendedNodeInfoRequest::parse() has parameter $data with no value type specified in iterable type array.

Check failure on line 21 in src/lib/REST/Input/Parser/ContentTree/LoadExtendedNodeInfoRequest.php

View workflow job for this annotation

GitHub Actions / Tests (8.0)

Method Ibexa\AdminUi\REST\Input\Parser\ContentTree\LoadExtendedNodeInfoRequest::parse() has parameter $data with no value type specified in iterable type array.

Check failure on line 21 in src/lib/REST/Input/Parser/ContentTree/LoadExtendedNodeInfoRequest.php

View workflow job for this annotation

GitHub Actions / Tests (8.1)

Method Ibexa\AdminUi\REST\Input\Parser\ContentTree\LoadExtendedNodeInfoRequest::parse() has parameter $data with no value type specified in iterable type array.
{
if (!array_key_exists('locationId', $data) || !is_int($data['locationId'])) {
throw new Exceptions\Parser(
sprintf("Missing or invalid 'locationId' property for %s.", self::class)
);
}

$locationId = $data['locationId'];

return new LoadNodeExtendedInfoRequestValue($locationId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?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\AdminUi\REST\Output\ValueObjectVisitor\ContentTree;

use Ibexa\Contracts\Rest\Output\Generator;
use Ibexa\Contracts\Rest\Output\ValueObjectVisitor;
use Ibexa\Contracts\Rest\Output\Visitor;
use Symfony\Component\HttpFoundation\Response;

/**
* @phpstan-import-type PermissionRestrictions from \Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo
*/
class NodeExtendedInfo extends ValueObjectVisitor
{
/**
* Visit struct returned by controllers.
*
* @param \Ibexa\Contracts\Rest\Output\Visitor $visitor
* @param \Ibexa\Contracts\Rest\Output\Generator $generator
* @param \Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo $data
*/
public function visit(Visitor $visitor, Generator $generator, $data): void
{
$generator->startObjectElement('ContentTreeNodeExtendedInfo');
$visitor->setHeader('Content-Type', $generator->getMediaType('ContentTreeNodeExtendedInfo'));
$visitor->setStatus(Response::HTTP_OK);

$this->buildPermissionNode($data->getPermissionRestrictions(), $generator);

$generator->endObjectElement('ContentTreeNodeExtendedInfo');
}

/**
* @phpstan-param PermissionRestrictions $permissionRestrictions
*/
protected function buildPermissionNode(
?array $permissionRestrictions,
Generator $generator
): void {
if (null === $permissionRestrictions) {
return;
}

$generator->startHashElement('permissions');

foreach ($permissionRestrictions as $function => $restrictions) {
$generator->startHashElement($function);
foreach ($restrictions as $restrictionKey => $restrictionValue) {
if (is_array($restrictionValue)) {
$generator->startList($restrictionKey);
foreach ($restrictionValue as $value) {
$generator->valueElement($restrictionKey, $value);
}
$generator->endList($restrictionKey);
} else {
$generator->valueElement($restrictionKey, $restrictionValue);
}
}
$generator->endHashElement($function);
}

$generator->endHashElement('permissions');
}
}
21 changes: 21 additions & 0 deletions src/lib/REST/Value/ContentTree/LoadNodeExtendedInfoRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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\AdminUi\REST\Value\ContentTree;

use Ibexa\Rest\Value as RestValue;

class LoadNodeExtendedInfoRequest extends RestValue
{
public int $locationId;

public function __construct(int $locationId)
{
$this->locationId = $locationId;
}
}
46 changes: 46 additions & 0 deletions src/lib/REST/Value/ContentTree/NodeExtendedInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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\AdminUi\REST\Value\ContentTree;

use Ibexa\Rest\Value as RestValue;

/**
* @phpstan-type Restrictions array{
* hasAccess: bool,
* restrictedContentTypeIds: array<int>,
* restrictedLanguageCodes: array<string>,
* }
*
* @phpstan-type PermissionRestrictions array{
* create: Restrictions,
* edit: Restrictions,
* }
*/
class NodeExtendedInfo extends RestValue
{
/** @phpstan-var PermissionRestrictions|null */
private ?array $permissions;

/**
* @phpstan-param PermissionRestrictions|null $permissions
*/
public function __construct(
?array $permissions = null,
) {
$this->permissions = $permissions;
}

/**
* @return PermissionRestrictions|null
*/
public function getPermissionRestrictions(): ?array
{
return $this->permissions;
}
}

0 comments on commit ab5a8ac

Please sign in to comment.