diff --git a/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php b/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php
index 7b941d86572..b014b810d82 100644
--- a/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php
+++ b/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php
@@ -32,9 +32,13 @@
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPublished;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceRebaseFailed;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRenamed;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceOwnerWasChanged;
use Neos\EventStore\Model\Event\EventData;
use Neos\EventStore\Model\Event;
use Neos\EventStore\Model\Event\EventType;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged;
/**
* Central authority to convert Content Repository domain events to Event Store EventData and EventType, vice versa.
@@ -85,11 +89,15 @@ public function __construct()
RootNodeAggregateDimensionsWereUpdated::class,
WorkspaceRebaseFailed::class,
WorkspaceWasCreated::class,
+ WorkspaceWasRenamed::class,
WorkspaceWasDiscarded::class,
WorkspaceWasPartiallyDiscarded::class,
WorkspaceWasPartiallyPublished::class,
WorkspaceWasPublished::class,
- WorkspaceWasRebased::class
+ WorkspaceWasRebased::class,
+ WorkspaceWasRemoved::class,
+ WorkspaceOwnerWasChanged::class,
+ WorkspaceBaseWorkspaceWasChanged::class,
];
foreach ($supportedEventClassNames as $fullEventClassName) {
diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php
index ecea56a49dc..51634eac3ba 100644
--- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php
+++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php
@@ -100,7 +100,8 @@ private function handleForkContentStream(
$sourceContentStreamVersion->unwrap(),
),
),
- ExpectedVersion::ANY()
+ // NO_STREAM to ensure the "fork" happens as the first event of the new content stream
+ ExpectedVersion::NO_STREAM()
);
}
diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php
index 182c9a55b07..ae56909cefc 100644
--- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php
+++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php
@@ -31,6 +31,7 @@
use Neos\ContentRepository\Core\Feature\ContentStreamCreation\Command\CreateContentStream;
use Neos\ContentRepository\Core\Feature\ContentStreamForking\Command\ForkContentStream;
use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked;
+use Neos\ContentRepository\Core\Feature\ContentStreamRemoval\Command\RemoveContentStream;
use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists;
use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet;
use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherContentStreamsInterface;
@@ -51,6 +52,17 @@
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\BaseWorkspaceDoesNotExist;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Exception\BaseWorkspaceHasBeenModifiedInTheMeantime;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\RenameWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRenamed;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeWorkspaceOwner;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceOwnerWasChanged;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeBaseWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\WorkspaceIsNotEmptyException;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\BaseWorkspaceEqualsWorkspaceException;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\CircularRelationBetweenWorkspacesException;
use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceHasNoBaseWorkspaceName;
use Neos\ContentRepository\Core\Projection\Workspace\Workspace;
@@ -61,6 +73,7 @@
use Neos\EventStore\Exception\ConcurrencyException;
use Neos\EventStore\Model\EventEnvelope;
use Neos\EventStore\Model\EventStream\ExpectedVersion;
+use Neos\EventStore\Model\Event\EventType;
/**
* @internal from userland, you'll use ContentRepository::handle to dispatch commands
@@ -84,12 +97,16 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo
/** @phpstan-ignore-next-line */
return match ($command::class) {
CreateWorkspace::class => $this->handleCreateWorkspace($command, $contentRepository),
+ RenameWorkspace::class => $this->handleRenameWorkspace($command, $contentRepository),
CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $contentRepository),
PublishWorkspace::class => $this->handlePublishWorkspace($command, $contentRepository),
RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $contentRepository),
PublishIndividualNodesFromWorkspace::class => $this->handlePublishIndividualNodesFromWorkspace($command, $contentRepository),
DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $contentRepository),
DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $contentRepository),
+ DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $contentRepository),
+ ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $contentRepository),
+ ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $contentRepository),
};
}
@@ -146,6 +163,28 @@ private function handleCreateWorkspace(
);
}
+ /**
+ * @throws WorkspaceDoesNotExist
+ */
+ private function handleRenameWorkspace(RenameWorkspace $command, ContentRepository $contentRepository): EventsToPublish
+ {
+ $this->requireWorkspace($command->workspaceName, $contentRepository);
+
+ $events = Events::with(
+ new WorkspaceWasRenamed(
+ $command->workspaceName,
+ $command->workspaceTitle,
+ $command->workspaceDescription,
+ )
+ );
+
+ return new EventsToPublish(
+ WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(),
+ $events,
+ ExpectedVersion::STREAM_EXISTS()
+ );
+ }
+
/**
* @param CreateRootWorkspace $command
* @return EventsToPublish
@@ -368,8 +407,8 @@ private function handleRebaseWorkspace(
$rebaseStatistics->commandRebaseError(sprintf(
"The content stream %s cannot be rebased. Error with command %d (%s)"
- . " - see nested exception for details.\n\n The base workspace %s is at content stream %s."
- . "\n The full list of commands applied so far is: %s",
+ . " - see nested exception for details.\n\n The base workspace %s is at content stream %s."
+ . "\n The full list of commands applied so far is: %s",
$workspaceContentStreamName->value,
$i,
get_class($commandToRebase),
@@ -693,6 +732,102 @@ private function handleDiscardWorkspace(
);
}
+ /**
+ * @throws BaseWorkspaceDoesNotExist
+ * @throws WorkspaceDoesNotExist
+ * @throws WorkspaceHasNoBaseWorkspaceName
+ * @throws WorkspaceIsNotEmptyException
+ * @throws BaseWorkspaceEqualsWorkspaceException
+ * @throws CircularRelationBetweenWorkspacesException
+ */
+ private function handleChangeBaseWorkspace(
+ ChangeBaseWorkspace $command,
+ ContentRepository $contentRepository,
+ ): EventsToPublish {
+ $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository);
+ $this->requireEmptyWorkspace($workspace);
+ $this->requireBaseWorkspace($workspace, $contentRepository);
+
+ $baseWorkspace = $this->requireWorkspace($command->baseWorkspaceName, $contentRepository);
+
+ $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository);
+
+ $contentRepository->handle(
+ new ForkContentStream(
+ $command->newContentStreamId,
+ $baseWorkspace->currentContentStreamId,
+ )
+ )->block();
+
+ $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName();
+ $events = Events::with(
+ new WorkspaceBaseWorkspaceWasChanged(
+ $command->workspaceName,
+ $command->baseWorkspaceName,
+ $command->newContentStreamId,
+ )
+ );
+
+ return new EventsToPublish(
+ $streamName,
+ $events,
+ ExpectedVersion::ANY()
+ );
+ }
+
+ /**
+ * @throws WorkspaceDoesNotExist
+ */
+ private function handleDeleteWorkspace(
+ DeleteWorkspace $command,
+ ContentRepository $contentRepository,
+ ): EventsToPublish {
+ $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository);
+
+ $contentRepository->handle(
+ new RemoveContentStream(
+ $workspace->currentContentStreamId
+ )
+ )->block();
+
+ $events = Events::with(
+ new WorkspaceWasRemoved(
+ $command->workspaceName,
+ )
+ );
+
+ $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName();
+ return new EventsToPublish(
+ $streamName,
+ $events,
+ ExpectedVersion::ANY()
+ );
+ }
+
+ /**
+ * @throws WorkspaceDoesNotExist
+ */
+ private function handleChangeWorkspaceOwner(
+ ChangeWorkspaceOwner $command,
+ ContentRepository $contentRepository,
+ ): EventsToPublish {
+ $this->requireWorkspace($command->workspaceName, $contentRepository);
+
+ $events = Events::with(
+ new WorkspaceOwnerWasChanged(
+ $command->workspaceName,
+ $command->newWorkspaceOwner
+ )
+ );
+
+ $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName();
+ return new EventsToPublish(
+ $streamName,
+ $events,
+ ExpectedVersion::STREAM_EXISTS()
+ );
+ }
+
/**
* @throws WorkspaceDoesNotExist
*/
@@ -723,4 +858,57 @@ private function requireBaseWorkspace(Workspace $workspace, ContentRepository $c
return $baseWorkspace;
}
+
+ /**
+ * @throws BaseWorkspaceEqualsWorkspaceException
+ * @throws CircularRelationBetweenWorkspacesException
+ */
+ private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, ContentRepository $contentRepository): void
+ {
+ if ($workspace->workspaceName->equals($baseWorkspace->workspaceName)) {
+ throw new BaseWorkspaceEqualsWorkspaceException(sprintf('The base workspace of the target must be different from the given workspace "%s".', $workspace->workspaceName->value));
+ }
+
+ $nextBaseWorkspace = $baseWorkspace;
+ while ($nextBaseWorkspace?->baseWorkspaceName !== null) {
+ if ($workspace->workspaceName->equals($nextBaseWorkspace->baseWorkspaceName)) {
+ throw new CircularRelationBetweenWorkspacesException(sprintf('The workspace "%s" is already on the path of the target workspace "%s".', $workspace->workspaceName->value, $baseWorkspace->workspaceName->value));
+ }
+ $nextBaseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($nextBaseWorkspace->baseWorkspaceName);
+ }
+ }
+
+ /**
+ * @throws WorkspaceIsNotEmptyException
+ */
+ private function requireEmptyWorkspace(Workspace $workspace): void
+ {
+ $workspaceContentStreamName = ContentStreamEventStreamName::fromContentStreamId(
+ $workspace->currentContentStreamId
+ );
+ if ($this->hasEventsInContentStreamExceptForking($workspaceContentStreamName)) {
+ throw new WorkspaceIsNotEmptyException('The user workspace needs to be empty before switching the base workspace.', 1681455989);
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ private function hasEventsInContentStreamExceptForking(
+ ContentStreamEventStreamName $workspaceContentStreamName,
+ ): bool {
+ $workspaceContentStream = $this->eventStore->load($workspaceContentStreamName->getEventStreamName());
+
+ $fullQualifiedEventClassName = ContentStreamWasForked::class;
+ $shortEventClassName = substr($fullQualifiedEventClassName, strrpos($fullQualifiedEventClassName, '\\') + 1);
+
+ foreach ($workspaceContentStream as $eventEnvelope) {
+ if ($eventEnvelope->event->type->value === EventType::fromString($shortEventClassName)->value) {
+ continue;
+ }
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeBaseWorkspace.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeBaseWorkspace.php
new file mode 100644
index 00000000000..c20569fb843
--- /dev/null
+++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Command/ChangeBaseWorkspace.php
@@ -0,0 +1,24 @@
+eventNormalizer->getEventClassName($event);
return in_array($eventClassName, [
WorkspaceWasCreated::class,
+ WorkspaceWasRenamed::class,
RootWorkspaceWasCreated::class,
WorkspaceWasDiscarded::class,
WorkspaceWasPartiallyDiscarded::class,
@@ -133,6 +138,9 @@ public function canHandle(Event $event): bool
WorkspaceWasPublished::class,
WorkspaceWasRebased::class,
WorkspaceRebaseFailed::class,
+ WorkspaceWasRemoved::class,
+ WorkspaceOwnerWasChanged::class,
+ WorkspaceBaseWorkspaceWasChanged::class,
]);
}
@@ -152,6 +160,8 @@ private function apply(EventEnvelope $eventEnvelope): void
if ($eventInstance instanceof WorkspaceWasCreated) {
$this->whenWorkspaceWasCreated($eventInstance);
+ } elseif ($eventInstance instanceof WorkspaceWasRenamed) {
+ $this->whenWorkspaceWasRenamed($eventInstance);
} elseif ($eventInstance instanceof RootWorkspaceWasCreated) {
$this->whenRootWorkspaceWasCreated($eventInstance);
} elseif ($eventInstance instanceof WorkspaceWasDiscarded) {
@@ -166,6 +176,12 @@ private function apply(EventEnvelope $eventEnvelope): void
$this->whenWorkspaceWasRebased($eventInstance);
} elseif ($eventInstance instanceof WorkspaceRebaseFailed) {
$this->whenWorkspaceRebaseFailed($eventInstance);
+ } elseif ($eventInstance instanceof WorkspaceWasRemoved) {
+ $this->whenWorkspaceWasRemoved($eventInstance);
+ } elseif ($eventInstance instanceof WorkspaceOwnerWasChanged) {
+ $this->whenWorkspaceOwnerWasChanged($eventInstance);
+ } elseif ($eventInstance instanceof WorkspaceBaseWorkspaceWasChanged) {
+ $this->whenWorkspaceBaseWorkspaceWasChanged($eventInstance);
} else {
throw new \RuntimeException('Not supported: ' . get_class($eventInstance));
}
@@ -206,6 +222,18 @@ private function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void
]);
}
+ private function whenWorkspaceWasRenamed(WorkspaceWasRenamed $event): void
+ {
+ $this->getDatabaseConnection()->update(
+ $this->tableName,
+ [
+ 'workspaceTitle' => $event->workspaceTitle->value,
+ 'workspaceDescription' => $event->workspaceDescription->value,
+ ],
+ ['workspaceName' => $event->workspaceName->value]
+ );
+ }
+
private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void
{
$this->getDatabaseConnection()->insert($this->tableName, [
@@ -277,9 +305,38 @@ private function whenWorkspaceRebaseFailed(WorkspaceRebaseFailed $event): void
$this->markWorkspaceAsOutdatedConflict($event->workspaceName);
}
+ private function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void
+ {
+ $this->getDatabaseConnection()->delete(
+ $this->tableName,
+ ['workspaceName' => $event->workspaceName->value]
+ );
+ }
+
+ private function whenWorkspaceOwnerWasChanged(WorkspaceOwnerWasChanged $event): void
+ {
+ $this->getDatabaseConnection()->update(
+ $this->tableName,
+ ['workspaceOwner' => $event->newWorkspaceOwner],
+ ['workspaceName' => $event->workspaceName->value]
+ );
+ }
+
+ private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void
+ {
+ $this->getDatabaseConnection()->update(
+ $this->tableName,
+ [
+ 'baseWorkspaceName' => $event->baseWorkspaceName->value,
+ 'currentContentStreamId' => $event->newContentStreamId->value,
+ ],
+ ['workspaceName' => $event->workspaceName->value]
+ );
+ }
+
private function updateContentStreamId(
ContentStreamId $contentStreamId,
- WorkspaceName $workspaceName
+ WorkspaceName $workspaceName,
): void {
$this->getDatabaseConnection()->update($this->tableName, [
'currentContentStreamId' => $contentStreamId->value,
diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php
index 96ee17b56cc..55f0f7319a7 100644
--- a/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php
+++ b/Neos.ContentRepository.Core/Classes/SharedModel/Workspace/WorkspaceDescription.php
@@ -38,4 +38,9 @@ public function jsonSerialize(): string
{
return $this->value;
}
+
+ public function equals(self $other): bool
+ {
+ return $this->value === $other->value;
+ }
}
diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php
index 964de307bb7..88499afa296 100644
--- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php
+++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentStreamCommandController.php
@@ -133,7 +133,7 @@ public function pruneCommand(string $contentRepositoryIdentifier = 'default', bo
$unusedContentStreams = $contentStreamPruner->prune($removeTemporary);
$unusedContentStreamsPresent = false;
foreach ($unusedContentStreams as $contentStream) {
- $this->outputFormatted('Removed %s', [$contentStream]);
+ $this->outputFormatted('Removed %s', [$contentStream->value]);
$unusedContentStreamsPresent = true;
}
if (!$unusedContentStreamsPresent) {
@@ -152,7 +152,7 @@ public function pruneRemovedFromEventStreamCommand(string $contentRepositoryIden
$unusedContentStreams = $contentStreamPruner->pruneRemovedFromEventStream();
$unusedContentStreamsPresent = false;
foreach ($unusedContentStreams as $contentStream) {
- $this->outputFormatted('Removed events for %s', [$contentStream]);
+ $this->outputFormatted('Removed events for %s', [$contentStream->value]);
$unusedContentStreamsPresent = true;
}
if (!$unusedContentStreamsPresent) {
diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
index d08c3ea6667..eabb52d7226 100644
--- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
+++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
@@ -29,6 +29,9 @@
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishIndividualNodesFromWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\RenameWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeWorkspaceOwner;
use Neos\Neos\FrontendRouting\NodeAddress;
use Neos\Neos\FrontendRouting\NodeAddressFactory;
use Neos\ContentRepository\Core\SharedModel\User\UserId;
@@ -48,7 +51,6 @@
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Package\PackageManager;
use Neos\Flow\Property\PropertyMapper;
-use Neos\Flow\Property\TypeConverter\PersistentObjectConverter;
use Neos\Flow\Security\Context;
use Neos\Media\Domain\Model\AssetInterface;
use Neos\Media\Domain\Model\ImageInterface;
@@ -213,7 +215,7 @@ public function createAction(
WorkspaceTitle $title,
WorkspaceName $baseWorkspace,
string $visibility,
- WorkspaceDescription $description
+ WorkspaceDescription $description,
) {
$contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())
->contentRepositoryId;
@@ -221,12 +223,12 @@ public function createAction(
$workspaceName = WorkspaceName::fromString(
Utility::renderValidNodeName($title->value) . '-'
- . substr(base_convert(microtime(false), 10, 36), -5, 5)
+ . substr(base_convert(microtime(false), 10, 36), -5, 5)
);
while ($contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) instanceof Workspace) {
$workspaceName = WorkspaceName::fromString(
Utility::renderValidNodeName($title->value) . '-'
- . substr(base_convert(microtime(false), 10, 36), -5, 5)
+ . substr(base_convert(microtime(false), 10, 36), -5, 5)
);
}
@@ -261,16 +263,16 @@ public function createAction(
/**
* Edit a workspace
*
- * @param WorkspaceName $workspace
+ * @param WorkspaceName $workspaceName
* @return void
*/
- public function editAction(WorkspaceName $workspace)
+ public function editAction(WorkspaceName $workspaceName)
{
$contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())
->contentRepositoryId;
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
- $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspace);
+ $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName);
if (is_null($workspace)) {
// @todo add flash message
$this->redirect('index');
@@ -287,38 +289,59 @@ public function editAction(WorkspaceName $workspace)
$this->view->assign('ownerOptions', $this->prepareOwnerOptions());
}
- /**
- * @return void
- */
- protected function initializeUpdateAction()
- {
- $converter = new PersistentObjectConverter();
- $this->arguments->getArgument('workspace')->getPropertyMappingConfiguration()
- ->forProperty('owner')
- ->setTypeConverter($converter)
- ->setTypeConverterOption(
- PersistentObjectConverter::class,
- PersistentObjectConverter::CONFIGURATION_TARGET_TYPE,
- User::class
- );
- parent::initializeAction();
- }
-
/**
* Update a workspace
*
- * @param Workspace $workspace A workspace to update
+ * @Flow\Validate(argumentName="title", type="\Neos\Flow\Validation\Validator\NotEmptyValidator")
+ * @param WorkspaceName $workspaceName
+ * @param WorkspaceTitle $title Human friendly title of the workspace, for example "Christmas Campaign"
+ * @param WorkspaceDescription $description A description explaining the purpose of the new workspace
+ * @param string $workspaceOwner Id of the owner of the workspace
* @return void
*/
- public function updateAction(Workspace $workspace)
+ public function updateAction(WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, ?string $workspaceOwner)
{
- #if ($workspace->getTitle() === '') {
- # $workspace->setTitle($workspace->getName());
- #}
- #$this->workspaceFinder->update($workspace);
+ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())
+ ->contentRepositoryId;
+ $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
+
+ if ($title->value === '') {
+ $title = WorkspaceTitle::fromString($workspaceName->value);
+ }
+
+ $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName);
+ if ($workspace === null) {
+ $this->addFlashMessage(
+ $this->getModuleLabel('workspaces.workspaceDoesNotExist'),
+ '',
+ Message::SEVERITY_ERROR
+ );
+ $this->redirect('index');
+ return;
+ }
+
+ if (!$workspace->workspaceTitle?->equals($title) || !$workspace->workspaceDescription->equals($description)) {
+ $contentRepository->handle(
+ new RenameWorkspace(
+ $workspaceName,
+ $title,
+ $description
+ )
+ )->block();
+ }
+
+ if ($workspace->workspaceOwner !== $workspaceOwner) {
+ $contentRepository->handle(
+ new ChangeWorkspaceOwner(
+ $workspaceName,
+ $workspaceOwner ?: null,
+ )
+ )->block();
+ }
+
$this->addFlashMessage($this->translator->translateById(
'workspaces.workspaceHasBeenUpdated',
- [$workspace->workspaceTitle?->value],
+ [$title->value],
null,
null,
'Modules',
@@ -330,17 +353,29 @@ public function updateAction(Workspace $workspace)
/**
* Delete a workspace
*
- * @param Workspace $workspace A workspace to delete
+ * @param WorkspaceName $workspaceName A workspace to delete
* @return void
*/
- public function deleteAction(Workspace $workspace)
+ public function deleteAction(WorkspaceName $workspaceName)
{
$contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())
->contentRepositoryId;
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
+ $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName);
+ if ($workspace === null) {
+ $this->addFlashMessage(
+ $this->getModuleLabel('workspaces.workspaceDoesNotExist'),
+ '',
+ Message::SEVERITY_ERROR
+ );
+ $this->redirect('index');
+ return;
+ }
+
if ($workspace->isPersonalWorkspace()) {
$this->redirect('index');
+ return;
}
$dependentWorkspaces = $contentRepository->getWorkspaceFinder()
@@ -365,35 +400,43 @@ public function deleteAction(Workspace $workspace)
}
$nodesCount = 0;
- /** @todo something else
+
try {
- $nodesCount = $this->publishingService->getUnpublishedNodesCount($workspace);
+ $nodesCount = $contentRepository->projectionState(ChangeFinder::class)
+ ->countByContentStreamId(
+ $workspace->currentContentStreamId
+ );
} catch (\Exception $exception) {
- $message = $this->translator->translateById(
- 'workspaces.notDeletedErrorWhileFetchingUnpublishedNodes',
- [(string)$workspace->$this->workspaceTitle],
- null,
- null,
- 'Modules',
- 'Neos.Neos'
- ) ?: 'workspaces.notDeletedErrorWhileFetchingUnpublishedNodes';
- $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
- $this->redirect('index');
- }*/
- //if ($nodesCount > 0) {
- $message = $this->translator->translateById(
- 'workspaces.workspaceCannotBeDeletedBecauseOfUnpublishedNodes',
- [$workspace->workspaceTitle?->value, $nodesCount],
- $nodesCount,
- null,
- 'Modules',
- 'Neos.Neos'
- ) ?: 'workspaces.workspaceCannotBeDeletedBecauseOfUnpublishedNodes';
- $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
- $this->redirect('index');
- //}
+ $message = $this->translator->translateById(
+ 'workspaces.notDeletedErrorWhileFetchingUnpublishedNodes',
+ [$workspace->workspaceTitle?->value],
+ null,
+ null,
+ 'Modules',
+ 'Neos.Neos'
+ ) ?: 'workspaces.notDeletedErrorWhileFetchingUnpublishedNodes';
+ $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
+ $this->redirect('index');
+ }
+ if ($nodesCount > 0) {
+ $message = $this->translator->translateById(
+ 'workspaces.workspaceCannotBeDeletedBecauseOfUnpublishedNodes',
+ [$workspace->workspaceTitle?->value, $nodesCount],
+ $nodesCount,
+ null,
+ 'Modules',
+ 'Neos.Neos'
+ ) ?: 'workspaces.workspaceCannotBeDeletedBecauseOfUnpublishedNodes';
+ $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
+ $this->redirect('index');
+ }
+
+ $contentRepository->handle(
+ new DeleteWorkspace(
+ $workspaceName,
+ )
+ )->block();
- //$this->workspaceFinder->remove($workspace);
$this->addFlashMessage($this->translator->translateById(
'workspaces.workspaceHasBeenRemoved',
[$workspace->workspaceTitle?->value],
@@ -422,22 +465,22 @@ public function rebaseAndRedirectAction(Node $targetNode, Workspace $targetWorks
/** @var Workspace $personalWorkspace */
/** @todo do something else
- if ($personalWorkspace !== $targetWorkspace) {
- if ($this->publishingService->getUnpublishedNodesCount($personalWorkspace) > 0) {
- $message = $this->translator->translateById(
- 'workspaces.cantEditBecauseWorkspaceContainsChanges',
- [],
- null,
- null,
- 'Modules',
- 'Neos.Neos'
- ) ?: 'workspaces.cantEditBecauseWorkspaceContainsChanges';
- $this->addFlashMessage($message, '', Message::SEVERITY_WARNING, [], 1437833387);
- $this->redirect('show', null, null, ['workspace' => $targetWorkspace]);
- }
- $personalWorkspace->setBaseWorkspace($targetWorkspace);
- $this->workspaceFinder->update($personalWorkspace);
- }
+ * if ($personalWorkspace !== $targetWorkspace) {
+ * if ($this->publishingService->getUnpublishedNodesCount($personalWorkspace) > 0) {
+ * $message = $this->translator->translateById(
+ * 'workspaces.cantEditBecauseWorkspaceContainsChanges',
+ * [],
+ * null,
+ * null,
+ * 'Modules',
+ * 'Neos.Neos'
+ * ) ?: 'workspaces.cantEditBecauseWorkspaceContainsChanges';
+ * $this->addFlashMessage($message, '', Message::SEVERITY_WARNING, [], 1437833387);
+ * $this->redirect('show', null, null, ['workspace' => $targetWorkspace]);
+ * }
+ * $personalWorkspace->setBaseWorkspace($targetWorkspace);
+ * $this->workspaceFinder->update($personalWorkspace);
+ * }
*/
$targetNodeAddressInPersonalWorkspace = new NodeAddress(
@@ -817,7 +860,7 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos
protected function getOriginalNode(
Node $modifiedNode,
ContentStreamId $baseContentStreamId,
- ContentRepository $contentRepository
+ ContentRepository $contentRepository,
): ?Node {
$baseSubgraph = $contentRepository->getContentGraph()->getSubgraph(
$baseContentStreamId,
@@ -838,7 +881,7 @@ protected function getOriginalNode(
protected function renderContentChanges(
Node $changedNode,
ContentStreamId $contentStreamIdOfOriginalNode,
- ContentRepository $contentRepository
+ ContentRepository $contentRepository,
): array {
$currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId(
$contentStreamIdOfOriginalNode
@@ -1021,7 +1064,7 @@ protected function postProcessDiffArray(array &$diffArray): void
*/
protected function prepareBaseWorkspaceOptions(
ContentRepository $contentRepository,
- Workspace $excludedWorkspace = null
+ Workspace $excludedWorkspace = null,
): array {
$baseWorkspaceOptions = [];
$workspaces = $contentRepository->getWorkspaceFinder()->findAll();
@@ -1061,7 +1104,7 @@ protected function prepareOwnerOptions(): array
private function getBaseWorkspaceWhenSureItExists(
Workspace $workspace,
- ContentRepository $contentRepository
+ ContentRepository $contentRepository,
): Workspace {
/** @var WorkspaceName $baseWorkspaceName We expect this to exist */
$baseWorkspaceName = $workspace->baseWorkspaceName;
diff --git a/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Edit.html b/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Edit.html
index 7cb613248dd..5e7b7e0bc91 100644
--- a/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Edit.html
+++ b/Neos.Neos/Resources/Private/Templates/Module/Management/Workspaces/Edit.html
@@ -2,38 +2,31 @@
- {neos:backend.translate(id: 'workspaces.editWorkspace', source: 'Modules', package: 'Neos.Neos', arguments: {0: workspace.title})}
+ {neos:backend.translate(id: 'workspaces.editWorkspace', source: 'Modules', package: 'Neos.Neos', arguments: {0: workspace.workspaceTitle.value})}
-
+
+