diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index 682c71b212e..f688b009f91 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -14,6 +14,8 @@ namespace Neos\Neos\Controller\Module\Management; +use Doctrine\DBAL\DBALException; +use JsonException; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdsToPublishOrDiscard; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdToPublishOrDiscard; @@ -78,35 +80,20 @@ class WorkspacesController extends AbstractModuleController #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; - /** - * @Flow\Inject - * @var SiteRepository - */ - protected $siteRepository; + #[Flow\Inject] + protected SiteRepository $siteRepository; - /** - * @Flow\Inject - * @var PropertyMapper - */ - protected $propertyMapper; + #[Flow\Inject] + protected PropertyMapper $propertyMapper; - /** - * @Flow\Inject - * @var Context - */ - protected $securityContext; + #[Flow\Inject] + protected Context $securityContext; - /** - * @Flow\Inject - * @var UserService - */ - protected $domainUserService; + #[Flow\Inject] + protected UserService $domainUserService; - /** - * @var PackageManager - * @Flow\Inject - */ - protected $packageManager; + #[Flow\Inject] + protected PackageManager $packageManager; /** * Display a list of unpublished content @@ -190,10 +177,7 @@ public function showAction(WorkspaceName $workspace): void ]); } - /** - * @return void - */ - public function newAction() + public function newAction(): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest()) ->contentRepositoryId; @@ -218,7 +202,7 @@ public function createAction( WorkspaceName $baseWorkspace, string $visibility, WorkspaceDescription $description, - ) { + ): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest()) ->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); @@ -264,11 +248,8 @@ public function createAction( /** * Edit a workspace - * - * @param WorkspaceName $workspaceName - * @return void */ - public function editAction(WorkspaceName $workspaceName) + public function editAction(WorkspaceName $workspaceName): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest()) ->contentRepositoryId; @@ -301,8 +282,12 @@ public function editAction(WorkspaceName $workspaceName) * @param string $workspaceOwner Id of the owner of the workspace * @return void */ - public function updateAction(WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, ?string $workspaceOwner) - { + public function updateAction( + WorkspaceName $workspaceName, + WorkspaceTitle $title, + WorkspaceDescription $description, + ?string $workspaceOwner + ): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest()) ->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); @@ -356,9 +341,12 @@ public function updateAction(WorkspaceName $workspaceName, WorkspaceTitle $title * Delete a workspace * * @param WorkspaceName $workspaceName A workspace to delete - * @return void + * @throws IndexOutOfBoundsException + * @throws InvalidFormatPlaceholderException + * @throws StopActionException + * @throws DBALException */ - public function deleteAction(WorkspaceName $workspaceName) + public function deleteAction(WorkspaceName $workspaceName): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest()) ->contentRepositoryId; @@ -724,6 +712,7 @@ public function discardWorkspaceAction(WorkspaceName $workspace): void * Computes the number of added, changed and removed nodes for the given workspace * * @return array + * @throws JsonException */ protected function computeChangesCount(Workspace $selectedWorkspace, ContentRepository $contentRepository): array { @@ -749,11 +738,10 @@ protected function computeChangesCount(Workspace $selectedWorkspace, ContentRepo /** * Builds an array of changes for sites in the given workspace * @return array + * @throws JsonException */ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepository $contentRepository): array { - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - $siteChanges = []; $changes = $contentRepository->projectionState(ChangeFinder::class) ->findByContentStreamId( @@ -792,7 +780,11 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos $documentPathSegments = []; foreach ($ancestors as $ancestor) { $pathSegment = $ancestor->nodeName ?: NodeName::fromString($ancestor->nodeAggregateId->value); - $nodePathSegments[] = $pathSegment; + // Don't include `sites` path as they are not needed + // by the HTML/JS magic and won't be included as `$documentPathSegments` + if (!$this->getNodeType($ancestor)->isOfType(NodeTypeNameFactory::NAME_SITES)) { + $nodePathSegments[] = $pathSegment; + } if ($this->getNodeType($ancestor)->isOfType(NodeTypeNameFactory::NAME_DOCUMENT)) { $documentPathSegments[] = $pathSegment; if (is_null($documentNode)) { @@ -808,29 +800,46 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos // We should probably throw an exception though if ($documentNode !== null && $siteNode !== null && $siteNode->nodeName) { $siteNodeName = $siteNode->nodeName->value; - $documentPath = implode('/', array_slice(array_map( + // Reverse `$documentPathSegments` to start with the site node. + // The paths are used for grouping the nodes and for selecting a tree of nodes. + $documentPath = implode('/', array_reverse(array_map( fn (NodeName $nodeName): string => $nodeName->value, $documentPathSegments - ), 2)); - $relativePath = str_replace( - sprintf('//%s/%s', $siteNodeName, $documentPath), - '', - implode('/', array_map( - fn (NodeName $nodeName): string => $nodeName->value, - $nodePathSegments - )) - ); + ))); + // Reverse `$nodePathSegments` to start with the site node. + // The paths are used for grouping the nodes and for selecting a tree of nodes. + $relativePath = implode('/', array_reverse(array_map( + fn (NodeName $nodeName): string => $nodeName->value, + $nodePathSegments + ))); if (!isset($siteChanges[$siteNodeName]['siteNode'])) { $siteChanges[$siteNodeName]['siteNode'] = $this->siteRepository->findOneByNodeName(SiteNodeName::fromString($siteNodeName)); } + $siteChanges[$siteNodeName]['documents'][$documentPath]['documentNode'] = $documentNode; + // We need to set `isNew` and `isMoved` on document level to make our JS behave as before. + if ($documentNode->equals($node)) { + $siteChanges[$siteNodeName]['documents'][$documentPath]['isNew'] = $change->created; + $siteChanges[$siteNodeName]['documents'][$documentPath]['isMoved'] = $change->moved; + } + + // As for changes of type `delete` we are using nodes from the live content stream + // we can't create `serializedNodeAddress` from the node. + // Instead, we use the original stored values. + $nodeAddress = new NodeAddress( + $change->contentStreamId, + $change->originDimensionSpacePoint->toDimensionSpacePoint(), + $change->nodeAggregateId, + $selectedWorkspace->workspaceName + ); $change = [ 'node' => $node, - 'serializedNodeAddress' => $nodeAddressFactory->createFromNode($node)->serializeForUri(), + 'serializedNodeAddress' => $nodeAddress->serializeForUri(), 'isRemoved' => $change->deleted, - 'isNew' => false, + 'isNew' => $change->created, + 'isMoved' => $change->moved, 'contentChanges' => $this->renderContentChanges( $node, $change->contentStreamId, @@ -848,19 +857,9 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos ksort($siteChanges); foreach ($siteChanges as $siteKey => $site) { - /*foreach ($site['documents'] as $documentKey => $document) { - $liveDocumentNode = $liveContext->getNodeByIdentifier($document['documentNode']->getIdentifier()); - $siteChanges[$siteKey]['documents'][$documentKey]['isMoved'] - = $liveDocumentNode && $document['documentNode']->getPath() !== $liveDocumentNode->getPath(); - $siteChanges[$siteKey]['documents'][$documentKey]['isNew'] = $liveDocumentNode === null; - foreach ($document['changes'] as $changeKey => $change) { - $liveNode = $liveContext->getNodeByIdentifier($change['node']->getIdentifier()); - $siteChanges[$siteKey]['documents'][$documentKey]['changes'][$changeKey]['isNew'] - = is_null($liveNode); - $siteChanges[$siteKey]['documents'][$documentKey]['changes'][$changeKey]['isMoved'] - = $liveNode && $change['node']->getPath() !== $liveNode->getPath(); - } - }*/ + foreach ($site['documents'] as $documentKey => $document) { + ksort($siteChanges[$siteKey]['documents'][$documentKey]['changes']); + } ksort($siteChanges[$siteKey]['documents']); } return $siteChanges; diff --git a/Neos.Neos/Resources/Private/Partials/Module/Management/Workspaces/ContentChangeAttributes.html b/Neos.Neos/Resources/Private/Partials/Module/Management/Workspaces/ContentChangeAttributes.html index 8322fe6ad2b..0568d7d1c06 100644 --- a/Neos.Neos/Resources/Private/Partials/Module/Management/Workspaces/ContentChangeAttributes.html +++ b/Neos.Neos/Resources/Private/Partials/Module/Management/Workspaces/ContentChangeAttributes.html @@ -1,16 +1,16 @@ {namespace neos=Neos\Neos\ViewHelpers} - class="neos-content-change legend-deleted" data-nodepath="{change.node.path}" title="{neos:backend.translate(id: 'workspaces.legend.deleted', source: 'Modules', package: 'Neos.Neos')}" + class="neos-content-change legend-deleted" data-nodepath="{nodepath}" title="{neos:backend.translate(id: 'workspaces.legend.deleted', source: 'Modules', package: 'Neos.Neos')}" - class="neos-content-change legend-created" data-nodepath="{change.node.path}" title="{neos:backend.translate(id: 'workspaces.legend.created', source: 'Modules', package: 'Neos.Neos')}" + class="neos-content-change legend-created" data-nodepath="{nodepath}" title="{neos:backend.translate(id: 'workspaces.legend.created', source: 'Modules', package: 'Neos.Neos')}" - class="neos-content-change legend-moved" data-nodepath="{change.node.path}" title="{neos:backend.translate(id: 'workspaces.legend.moved', source: 'Modules', package: 'Neos.Neos')}" + class="neos-content-change legend-moved" data-nodepath="{nodepath}" title="{neos:backend.translate(id: 'workspaces.legend.moved', source: 'Modules', package: 'Neos.Neos')}" - class="neos-content-change legend-hidden" data-nodepath="{change.node.path}" title="{neos:backend.translate(id: 'workspaces.legend.hidden', source: 'Modules', package: 'Neos.Neos')}" - class="neos-content-change legend-edited" data-nodepath="{change.node.path}" title="{neos:backend.translate(id: 'workspaces.legend.edited', source: 'Modules', package: 'Neos.Neos')}" + class="neos-content-change legend-hidden" data-nodepath="{nodepath}" title="{neos:backend.translate(id: 'workspaces.legend.hidden', source: 'Modules', package: 'Neos.Neos')}" + class="neos-content-change legend-edited" data-nodepath="{nodepath}" title="{neos:backend.translate(id: 'workspaces.legend.edited', source: 'Modules', package: 'Neos.Neos')}" diff --git a/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Show.html b/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Show.html index ec5e58e00d4..f3c0bf613b8 100644 --- a/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Show.html +++ b/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Show.html @@ -28,11 +28,11 @@ - + - + @@ -56,15 +56,15 @@ - + - + - +