Skip to content

Commit

Permalink
FEATURE: Create query endpoints for node tree
Browse files Browse the repository at this point in the history
  • Loading branch information
grebaldi committed May 1, 2024
1 parent 86bdf62 commit 1abc6c7
Show file tree
Hide file tree
Showing 16 changed files with 1,002 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ControllerInterface;
use Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQuery;
use Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQueryHandler;

#[Flow\Scope("singleton")]
final class GetChildrenForTreeNodeController implements ControllerInterface
{
#[Flow\Inject]
protected GetChildrenForTreeNodeQueryHandler $queryHandler;

public function processRequest(ActionRequest $request, ActionResponse $response)
{
$request->setDispatched(true);

$query = $request->getArguments();
$query = GetChildrenForTreeNodeQuery::fromArray($query);

$queryResult = $this->queryHandler->handle($query);

$response->setContentType('application/json');
$response->setContent(json_encode([
'success' => $queryResult
], JSON_THROW_ON_ERROR));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Domain\NodeAggregate\NodeAggregateIdentifier;
use Neos\ContentRepository\Domain\NodeType\NodeTypeName;
use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQuery
{
/**
* @param array<string,array<int,string>> $dimensionValues
*/
public function __construct(
public readonly string $workspaceName,
public readonly array $dimensionValues,
public readonly NodeAggregateIdentifier $treeNodeId,
public readonly string $nodeTypeFilter,
) {
}

/**
* @param array<string,mixed> $array
*/
public static function fromArray(array $array): self
{
isset($array['workspaceName'])
or throw new \Exception('Workspace name must be set');
is_string($array['workspaceName'])
or throw new \Exception('Workspace name must be a string');

isset($array['dimensionValues'])
or throw new \Exception('Dimension values must be set');
is_array($array['dimensionValues'])
or throw new \Exception('Dimension values must be an array');

isset($array['treeNodeId'])
or throw new \Exception('Tree node id must be set');
is_string($array['treeNodeId'])
or throw new \Exception('Tree node id must be a string');

!isset($array['nodeTypeFilter']) or is_string($array['nodeTypeFilter'])
or throw new \Exception('Node type filter must be a string');

return new self(
workspaceName: $array['workspaceName'],
dimensionValues: $array['dimensionValues'],
treeNodeId: NodeAggregateIdentifier::fromString($array['treeNodeId']),
nodeTypeFilter: $array['nodeTypeFilter'] ?? '',
);
}

/**
* @return array<string,string>
*/
public function getTargetDimensionValues(): array
{
$result = [];

foreach ($this->dimensionValues as $dimensionName => $fallbackChain) {
$result[$dimensionName] = $fallbackChain[0] ?? '';
}

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use GuzzleHttp\Psr7\Uri;
use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Model\NodeType;
use Neos\ContentRepository\Domain\NodeType\NodeTypeName;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Sitegeist\Archaeopteryx\Application\Shared\NodeTypeFilterOptions;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNode;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNodes;

/**
* @internal
*/
#[Flow\Scope("singleton")]
final class GetChildrenForTreeNodeQueryHandler
{
#[Flow\Inject]
protected ContentContextFactory $contentContextFactory;

#[Flow\Inject]
protected NodeTypeManager $nodeTypeManager;

public function handle(GetChildrenForTreeNodeQuery $query): GetChildrenForTreeNodeQueryResult
{
$contentContext = $this->contentContextFactory->create([
'workspaceName' => $query->workspaceName,
'dimensions' => $query->dimensionValues,
'targetDimensions' => $query->getTargetDimensionValues(),
'invisibleContentShown' => true,
'removedContentShown' => false,
'inaccessibleContentShown' => true
]);

$node = $contentContext->getNodeByIdentifier((string) $query->treeNodeId);
if (!$node instanceof Node) {
throw new \Exception('Forget it!');
}

return new GetChildrenForTreeNodeQueryResult(
children: $children = $this->createTreeNodesFromChildrenOfNode($node, $query),
additionalNodeTypeFilterOptions: NodeTypeFilterOptions::fromTreeNodes($children, $this->nodeTypeManager)
);
}

private function createTreeNodesFromChildrenOfNode(Node $node, GetChildrenForTreeNodeQuery $query): TreeNodes
{
$items = [];

foreach ($node->getChildNodes($query->nodeTypeFilter) as $childNode) {
/** @var Node $childNode */
$items[] = $this->createTreeNodeFromNode($childNode, $query);
}

return new TreeNodes(...$items);
}

private function createTreeNodeFromNode(Node $node, GetChildrenForTreeNodeQuery $query): TreeNode
{
return new TreeNode(
nodeAggregateIdentifier: $node->getNodeAggregateIdentifier(),
uri: new Uri('node://' . $node->getNodeAggregateIdentifier()),
icon: $node->getNodeType()->getConfiguration('ui.icon'),
label: $node->getLabel(),
isMatchedByFilter: true,
isDisabled: $node->isHidden(),
isHiddenInMenu: $node->isHiddenInIndex(),
hasScheduledDisabledState:
$node->getHiddenBeforeDateTime() !== null
|| $node->getHiddenAfterDateTime() !== null,
hasUnloadedChildren: $node->getNumberOfChildNodes($query->nodeTypeFilter) > 0,
nodeTypeNames: iterator_to_array(
$this->getAllNonAbstractSuperTypesOf($node->getNodeType()),
false
),
children: new TreeNodes(),
);
}

/**
* @return \Traversable<int,NodeTypeName>
*/
private function getAllNonAbstractSuperTypesOf(NodeType $nodeType): \Traversable
{
if (!$nodeType->isAbstract()) {
yield NodeTypeName::fromString($nodeType->getName());
}

foreach ($nodeType->getDeclaredSuperTypes() as $superType) {
yield from $this->getAllNonAbstractSuperTypesOf($superType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetChildrenForTreeNode;

use Neos\Flow\Annotations as Flow;
use Sitegeist\Archaeopteryx\Application\Shared\NodeTypeFilterOptions;
use Sitegeist\Archaeopteryx\Application\Shared\TreeNodes;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQueryResult implements \JsonSerializable
{
public function __construct(
public readonly TreeNodes $children,
public readonly NodeTypeFilterOptions $additionalNodeTypeFilterOptions,
) {
}

public function jsonSerialize(): mixed
{
return get_object_vars($this);
}
}
42 changes: 42 additions & 0 deletions Classes/Application/GetTree/Controller/GetTreeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This script belongs to the package "Sitegeist.Archaeopteryx".
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Archaeopteryx\Application\GetTree\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ControllerInterface;
use Sitegeist\Archaeopteryx\Application\GetTree\GetTreeQuery;
use Sitegeist\Archaeopteryx\Application\GetTree\GetTreeQueryHandler;

#[Flow\Scope("singleton")]
final class GetTreeController implements ControllerInterface
{
#[Flow\Inject]
protected GetTreeQueryHandler $queryHandler;

public function processRequest(ActionRequest $request, ActionResponse $response)
{
$request->setDispatched(true);

$query = $request->getArguments();
$query = GetTreeQuery::fromArray($query);

$queryResult = $this->queryHandler->handle($query);

$response->setContentType('application/json');
$response->setContent(json_encode([
'success' => $queryResult
], JSON_THROW_ON_ERROR));
}
}
Loading

0 comments on commit 1abc6c7

Please sign in to comment.