Skip to content

Commit 2300df2

Browse files
committed
TASK: Add node state information isCreated, isModified and isDeleted to TreeNode
Information is obtained from the PendingChangeProjection. Nodes that are removed but not yet published in the current workspace will still show up but not their children that only inherited the removed tag
1 parent 529c493 commit 2300df2

File tree

9 files changed

+795
-62
lines changed

9 files changed

+795
-62
lines changed

Classes/Application/Shared/TreeNode.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public function __construct(
3131
public readonly bool $isMatchedByFilter,
3232
public readonly bool $isDisabled,
3333
public readonly bool $isHiddenInMenu,
34+
public readonly bool $isCreated,
35+
public readonly bool $isModified,
36+
public readonly bool $isDeleted,
3437
// todo rename to hasTimeableNodeVisibility?
3538
public readonly bool $hasScheduledDisabledState,
3639
public readonly bool $hasUnloadedChildren,
@@ -48,6 +51,9 @@ public function jsonSerialize(): mixed
4851
'isMatchedByFilter' => $this->isMatchedByFilter,
4952
'isDisabled' => $this->isDisabled,
5053
'isHiddenInMenu' => $this->isHiddenInMenu,
54+
'isCreated' => $this->isCreated,
55+
'isModified' => $this->isModified,
56+
'isDeleted' => $this->isDeleted,
5157
'hasScheduledDisabledState' => $this->hasScheduledDisabledState,
5258
'hasUnloadedChildren' => $this->hasUnloadedChildren,
5359
'children' => $this->children,

Classes/Application/Shared/TreeNodeBuilder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public function __construct(
3838
private bool $isMatchedByFilter,
3939
private readonly bool $isDisabled,
4040
private readonly bool $isHiddenInMenu,
41+
private bool $isCreated,
42+
private bool $isModified,
43+
private bool $isDeleted,
4144
private readonly bool $hasScheduledDisabledState,
4245
private bool $hasUnloadedChildren,
4346
) {
@@ -89,6 +92,9 @@ public function build(): TreeNode
8992
isMatchedByFilter: $this->isMatchedByFilter,
9093
isDisabled: $this->isDisabled,
9194
isHiddenInMenu: $this->isHiddenInMenu,
95+
isCreated: $this->isCreated,
96+
isModified: $this->isModified,
97+
isDeleted: $this->isDeleted,
9298
hasScheduledDisabledState: $this->hasScheduledDisabledState,
9399
hasUnloadedChildren: $this->hasUnloadedChildren,
94100
children: $this->buildChildren(),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neos.Neos.Ui package.
5+
*
6+
* (c) Contributors of the Neos Project - www.neos.io
7+
*
8+
* This package is Open Source Software. For the full copyright and license
9+
* information, please view the LICENSE file which was distributed with this
10+
* source code.
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
namespace Neos\Neos\Ui\Infrastructure\ESCR;
16+
17+
use Neos\Neos\PendingChangesProjection\Change;
18+
19+
readonly class NodeChangeState
20+
{
21+
public function __construct(
22+
public bool $isCreated,
23+
public bool $isChanged,
24+
public bool $isDeleted,
25+
) {
26+
}
27+
28+
public static function create(): self
29+
{
30+
return new self(false, false, false);
31+
}
32+
33+
public static function fromChange(Change $change): self
34+
{
35+
return new self(
36+
$change->created,
37+
$change->changed || $change->deleted,
38+
$change->deleted,
39+
);
40+
}
41+
42+
public function withAppliedAdditionalChange(Change $change): self
43+
{
44+
return new self(
45+
$change->created || $this->isCreated,
46+
($change->changed || $change->moved) || $this->isChanged,
47+
$change->deleted || $this->isDeleted,
48+
);
49+
}
50+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neos.Neos.Ui package.
5+
*
6+
* (c) Contributors of the Neos Project - www.neos.io
7+
*
8+
* This package is Open Source Software. For the full copyright and license
9+
* information, please view the LICENSE file which was distributed with this
10+
* source code.
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
namespace Neos\Neos\Ui\Infrastructure\ESCR;
16+
17+
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
18+
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
19+
use Neos\Flow\Annotations as Flow;
20+
use Neos\Neos\PendingChangesProjection\Changes;
21+
22+
#[Flow\Proxy(false)]
23+
final readonly class NodeChangeStateCollection
24+
{
25+
/**
26+
* @param array<string, NodeChangeState> $changesByNodeAggregateId
27+
*/
28+
private function __construct(
29+
private array $changesByNodeAggregateId,
30+
) {
31+
}
32+
33+
public static function create(Changes $changes, DimensionSpacePoint $dimensionSpacePoint): self
34+
{
35+
/**
36+
* @var array<string, NodeChangeState> $changesByNodeAggregateId
37+
*/
38+
$changesByNodeAggregateId = [];
39+
40+
foreach ($changes as $change) {
41+
if ($change->originDimensionSpacePoint === null || $change->originDimensionSpacePoint->equals($dimensionSpacePoint)) {
42+
if ($pendingChange = $changesByNodeAggregateId[$change->nodeAggregateId->value] ?? null) {
43+
$changesByNodeAggregateId[$change->nodeAggregateId->value] = $pendingChange->withAppliedAdditionalChange($change);
44+
} else {
45+
$changesByNodeAggregateId[$change->nodeAggregateId->value] = NodeChangeState::fromChange($change);
46+
}
47+
}
48+
}
49+
return new self(
50+
$changesByNodeAggregateId
51+
);
52+
}
53+
54+
public function findByNodeAggreqateId(NodeAggregateId $nodeAggregateId): ?NodeChangeState
55+
{
56+
return $this->changesByNodeAggregateId[$nodeAggregateId->value] ?? null;
57+
}
58+
}

Classes/Infrastructure/ESCR/NodeService.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
use Neos\Flow\Annotations as Flow;
3434
use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface;
3535
use Neos\Neos\Domain\SubtreeTagging\NeosSubtreeTag;
36+
use Neos\Neos\PendingChangesProjection\Changes;
3637
use Neos\Neos\Ui\Application\Shared\NodeTypeWasNotFound;
3738
use Neos\Neos\Ui\Application\Shared\NodeWasNotFound;
3839
use Neos\Neos\Ui\Application\Shared\TreeNodeBuilder;
40+
use Neos\Workspace\Ui\ViewModel\PendingChanges;
3941

4042
/**
4143
* @internal
@@ -46,7 +48,8 @@ final class NodeService
4648
public function __construct(
4749
private readonly ContentRepository $contentRepository,
4850
public readonly ContentSubgraphInterface $subgraph,
49-
private readonly NodeLabelGeneratorInterface $nodeLabelGenerator
51+
private readonly NodeLabelGeneratorInterface $nodeLabelGenerator,
52+
public readonly NodeChangeStateCollection $pendingChanges,
5053
) {
5154
}
5255

@@ -88,26 +91,30 @@ public function getLabelForNode(Node $node): string
8891

8992
public function findParentNode(Node $node): ?Node
9093
{
91-
return $this->subgraph->findParentNode($node->aggregateId);
94+
$parent = $this->subgraph->findParentNode($node->aggregateId);
95+
return $parent && $this->filterRemovedNodes($parent) ? $parent : null;
9296
}
9397

9498
public function findPrecedingSiblingNodes(Node $node): Nodes
9599
{
96100
$filter = FindPrecedingSiblingNodesFilter::create();
97101

98-
return $this->subgraph->findPrecedingSiblingNodes($node->aggregateId, $filter);
102+
return $this->subgraph->findPrecedingSiblingNodes($node->aggregateId, $filter)
103+
->filter(fn(Node $node) => $this->filterRemovedNodes($node));
99104
}
100105

101106
public function findSucceedingSiblingNodes(Node $node): Nodes
102107
{
103108
$filter = FindSucceedingSiblingNodesFilter::create();
104109

105-
return $this->subgraph->findSucceedingSiblingNodes($node->aggregateId, $filter);
110+
return $this->subgraph->findSucceedingSiblingNodes($node->aggregateId, $filter)
111+
->filter(fn(Node $node) => $this->filterRemovedNodes($node));
106112
}
107113

108114
public function findNodeByAbsoluteNodePath(AbsoluteNodePath $path): ?Node
109115
{
110-
return $this->subgraph->findNodeByAbsolutePath($path);
116+
$node = $this->subgraph->findNodeByAbsolutePath($path);
117+
return $node && $this->filterRemovedNodes($node) ? $node : null;
111118
}
112119

113120
public function search(Node $rootNode, string $searchTerm, NodeTypeFilter $nodeTypeFilter): Nodes
@@ -117,7 +124,8 @@ public function search(Node $rootNode, string $searchTerm, NodeTypeFilter $nodeT
117124
searchTerm: $searchTerm
118125
);
119126

120-
return $this->subgraph->findDescendantNodes($rootNode->aggregateId, $filter);
127+
return $this->subgraph->findDescendantNodes($rootNode->aggregateId, $filter)
128+
->filter(fn(Node $node) => $this->filterRemovedNodes($node));
121129
}
122130

123131
public function createTreeBuilderForRootNode(
@@ -135,6 +143,7 @@ public function createTreeBuilderForRootNode(
135143
public function createTreeNodeBuilderForNode(Node $node): TreeNodeBuilder
136144
{
137145
$nodeType = $this->requireNodeTypeByName($node->nodeTypeName);
146+
$pendingChange = $this->pendingChanges->findByNodeAggreqateId($node->aggregateId);
138147

139148
return new TreeNodeBuilder(
140149
nodeAddress: NodeAddress::fromNode($node),
@@ -147,7 +156,10 @@ public function createTreeNodeBuilderForNode(Node $node): TreeNodeBuilder
147156
hasScheduledDisabledState:
148157
$node->getProperty('enableAfterDateTime') instanceof \DateTimeInterface
149158
|| $node->getProperty('disableAfterDateTime') instanceof \DateTimeInterface,
150-
hasUnloadedChildren: false
159+
hasUnloadedChildren: false,
160+
isCreated: $pendingChange ? $pendingChange->isCreated : false,
161+
isModified: $pendingChange ? $pendingChange->isChanged : false,
162+
isDeleted: $pendingChange ? $pendingChange->isDeleted : false,
151163
);
152164
}
153165

@@ -157,7 +169,8 @@ public function findChildNodes(Node $parentNode, NodeTypeCriteria $nodeTypeCrite
157169
nodeTypes: $nodeTypeCriteria,
158170
);
159171

160-
return $this->subgraph->findChildNodes($parentNode->aggregateId, $filter);
172+
return $this->subgraph->findChildNodes($parentNode->aggregateId, $filter)
173+
->filter(fn(Node $node) => $this->filterRemovedNodes($node));
161174
}
162175

163176
public function getNumberOfChildNodes(Node $parentNode, NodeTypeCriteria $nodeTypeCriteria): int
@@ -173,6 +186,24 @@ public function findAncestorNodes(Node $node): Nodes
173186
{
174187
$filter = FindAncestorNodesFilter::create();
175188

176-
return $this->subgraph->findAncestorNodes($node->aggregateId, $filter);
189+
return $this->subgraph->findAncestorNodes($node->aggregateId, $filter); }
190+
191+
/**
192+
* Filter function to remove all nodes that are tagged as removed
193+
* AND do not have the tag assigned directly (deleted as children)
194+
* AND are not in the current changes (other workspaces)
195+
*/
196+
public function filterRemovedNodes(Node $node): bool
197+
{
198+
if (!$node->tags->contain(NeosSubtreeTag::removed())) {
199+
return true;
200+
}
201+
if ($node->tags->withoutInherited()->contain(NeosSubtreeTag::removed())) {
202+
$changeState = $this->pendingChanges->findByNodeAggreqateId($node->aggregateId);
203+
if ($changeState instanceof NodeChangeState && $changeState->isDeleted) {
204+
return true;
205+
}
206+
}
207+
return false;
177208
}
178209
}

Classes/Infrastructure/ESCR/NodeServiceFactory.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
namespace Neos\Neos\Ui\Infrastructure\ESCR;
1616

1717
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
18+
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
1819
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
1920
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
2021
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
2122
use Neos\Flow\Annotations as Flow;
2223
use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface;
2324
use Neos\Neos\Domain\SubtreeTagging\NeosVisibilityConstraints;
25+
use Neos\Neos\PendingChangesProjection\ChangeFinder;
26+
use Neos\Neos\PendingChangesProjection\ChangeProjection;
2427

2528
/**
2629
* @internal
@@ -41,17 +44,25 @@ public function create(
4144
): NodeService {
4245
$contentRepository = $this->contentRepositoryRegistry
4346
->get($contentRepositoryId);
44-
$subgraph = $contentRepository
45-
->getContentGraph($workspaceName)
47+
$contentGraph = $contentRepository->getContentGraph($workspaceName);
48+
$subgraph = $contentGraph
4649
->getSubgraph(
4750
$dimensionSpacePoint,
48-
NeosVisibilityConstraints::excludeRemoved()
51+
VisibilityConstraints::createEmpty()
4952
);
5053

54+
$changeFinder = $contentRepository->projectionState(ChangeFinder::class);
55+
$changes = $changeFinder->findByContentStreamId($contentGraph->getContentStreamId());
56+
$pendingChanges = NodeChangeStateCollection::create(
57+
$changes,
58+
$dimensionSpacePoint,
59+
);
60+
5161
return new NodeService(
5262
contentRepository: $contentRepository,
5363
subgraph: $subgraph,
5464
nodeLabelGenerator: $this->nodeLabelGenerator,
65+
pendingChanges: $pendingChanges,
5566
);
5667
}
5768
}

0 commit comments

Comments
 (0)