diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index d2b530d7333..433fd9f6322 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -43,6 +43,7 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -171,6 +172,7 @@ public function canHandle(EventInterface $event): bool RootNodeAggregateWithNodeWasCreated::class, SubtreeWasTagged::class, SubtreeWasUntagged::class, + WorkspaceWasPartiallyDiscarded::class ]); } @@ -195,6 +197,9 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void RootNodeAggregateWithNodeWasCreated::class => $this->whenRootNodeAggregateWithNodeWasCreated($event, $eventEnvelope), SubtreeWasTagged::class => $this->whenSubtreeWasTagged($event), SubtreeWasUntagged::class => $this->whenSubtreeWasUntagged($event), + // workaround to allow the content graph catchuphook to react on this already now + // with https://github.com/neos/neos-development-collection/pull/5096 we will really handle the event here as well + WorkspaceWasPartiallyDiscarded::class => null, default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index c71a2965ca2..3f1595bb89c 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -17,8 +17,10 @@ use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamAndNodeAggregateId; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; use Neos\ContentRepository\Core\Projection\CatchUpHookInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventEnvelope; @@ -133,6 +135,16 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even return; } + if ($eventInstance instanceof WorkspaceWasPartiallyDiscarded) { + foreach ($eventInstance->discardedNodes as $discardedNode) { + $this->scheduleCacheFlushJobForNodeAggregate( + $this->contentRepository, + $eventInstance->workspaceName, + $discardedNode->nodeAggregateId + ); + } + } + if ( $eventInstance instanceof NodeAggregateWasRemoved // NOTE: when moving a node, we need to clear the cache not just after the move was completed, @@ -140,12 +152,11 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even // cleared, leading to presumably duplicate nodes in the UI. || $eventInstance instanceof NodeAggregateWasMoved ) { - $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); - if ($workspace === null) { + try { + $contentGraph = $this->contentRepository->getContentGraph($eventInstance->workspaceName); + } catch (WorkspaceDoesNotExist) { return; } - // FIXME: EventInterface->workspaceName - $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); @@ -157,7 +168,7 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even assert($parentNodeAggregate instanceof NodeAggregate); $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, - $workspace->workspaceName, + $eventInstance->workspaceName, $parentNodeAggregate->nodeAggregateId ); } @@ -177,19 +188,25 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event !($eventInstance instanceof NodeAggregateWasRemoved) && $eventInstance instanceof EmbedsContentStreamAndNodeAggregateId ) { - $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); - if ($workspace === null) { + // Hack, we don't declare the `workspaceName` as part of the `EmbedsContentStreamAndNodeAggregateId` events, + // but it will always exist! + // See https://github.com/neos/neos-development-collection/issues/5152 + /** @phpstan-ignore property.notFound */ + $workspaceName = $eventInstance->workspaceName; + + try { + $contentGraph = $this->contentRepository->getContentGraph($workspaceName); + } catch (WorkspaceDoesNotExist) { return; } - // FIXME: EventInterface->workspaceName - $nodeAggregate = $this->contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( + $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); if ($nodeAggregate) { $this->scheduleCacheFlushJobForNodeAggregate( $this->contentRepository, - $workspace->workspaceName, + $workspaceName, $nodeAggregate->nodeAggregateId ); } diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInUserWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInUserWorkspace.feature new file mode 100644 index 00000000000..63db9d29952 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInUserWorkspace.feature @@ -0,0 +1,195 @@ +@flowEntities +Feature: Tests for the ContentCacheFlusher and cache flushing when applied in user workspaces + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:ContentCollection': + constraints: + nodeTypes: + 'Neos.Neos:Document': false + '*': true + 'Neos.Neos:Content': + constraints: + nodeTypes: + '*': false + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentTypeWithMainCollection': + superTypes: + 'Neos.Neos:Document': true + childNodes: + main: + type: 'Neos.Neos:ContentCollection' + 'Neos.Neos:Test.TextNode': + superTypes: + 'Neos.Neos:Content': true + properties: + text: + type: string + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "editor" + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-editor" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-editor-cs-identifier" | + And I am in workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | tetheredDescendantNodeAggregateIds | + | site | root | Neos.Neos:Site | {} | site | {} | + | test-document-with-contents | site | Neos.Neos:Test.DocumentTypeWithMainCollection | {"uriPathSegment": "test-document-with-contents", "title": "Test document with contents"} | test-document-with-contents | {"main": "test-document-with-contents--main"} | + | text-node-start | test-document-with-contents--main | Neos.Neos:Test.TextNode | {"text": "Text Node at the start of the document"} | text-node-start | {} | + | text-node-end | test-document-with-contents--main | Neos.Neos:Test.TextNode | {"text": "Text Node at the end of the document"} | text-node-end | {} | + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-editor" | + And A site exists for node name "site" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + '*': + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + And the Fusion context node is "site" + And the Fusion context request URI is "http://localhost" + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.TextNode) < prototype(Neos.Neos:ContentComponent) { + renderer = ${"[" + q(node).property("text") + "]"} + } + """ + + Scenario: ContentCache gets flushed when a node that was just created gets discarded + Given I have Fusion content cache enabled + And I am in workspace "user-editor" and dimension space point {} + And the Fusion context node is "test-document-with-contents" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:ContentCollection { + nodePath = "main" + } + """ + Then I expect the following Fusion rendering result: + """ +