From 7e7905806b44a293e360917c5f6c065f1af2f728 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 29 Oct 2024 17:05:22 +0100 Subject: [PATCH 1/5] FEATURE: Allow mapping of legacy root paths to RootNodeAggregate NodeTypeNames --- .../Classes/Command/CrCommandController.php | 6 ++- .../Classes/LegacyMigrationService.php | 3 +- .../Classes/LegacyMigrationServiceFactory.php | 2 + .../Classes/NodeDataToEventsProcessor.php | 40 +++++++++---------- .../Configuration/Settings.yaml | 6 +++ .../Behavior/Bootstrap/FeatureContext.php | 2 + 6 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 Neos.ContentRepository.LegacyNodeMigration/Configuration/Settings.yaml diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php index 337a3aa66c..365a9d5312 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php @@ -25,6 +25,7 @@ use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\ContentRepositoryRegistry\Factory\EventStore\DoctrineEventStoreFactory; use Neos\ContentRepositoryRegistry\Service\ProjectionReplayServiceFactory; +use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Flow\Property\PropertyMapper; @@ -48,6 +49,8 @@ public function __construct( private readonly ContentRepositoryRegistry $contentRepositoryRegistry, private readonly SiteRepository $siteRepository, private readonly ProjectionReplayServiceFactory $projectionReplayServiceFactory, + #[Flow\InjectConfiguration(path: "rootNodeMapping")] + private readonly array $rootNodeTypeMappingByContentRepository, ) { parent::__construct(); } @@ -137,7 +140,8 @@ public function migrateLegacyDataCommand(bool $verbose = false, string $config = $this->resourceRepository, $this->resourceManager, $this->propertyMapper, - $liveContentStreamId + $liveContentStreamId, + $this->rootNodeTypeMappingByContentRepository[$contentRepositoryId->value] ?? [], ) ); assert($legacyMigrationService instanceof LegacyMigrationService); diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php index e95c5b27dc..c4e6bb626e 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php @@ -58,6 +58,7 @@ public function __construct( private readonly PropertyConverter $propertyConverter, private readonly EventStoreInterface $eventStore, private readonly ContentStreamId $contentStreamId, + private readonly array $rootNodeTypeMapping ) { } @@ -73,7 +74,7 @@ public function runAllProcessors(\Closure $outputLineFn, bool $verbose = false): /** @var ProcessorInterface[] $processors */ $processors = [ 'Exporting assets' => new NodeDataToAssetsProcessor($this->nodeTypeManager, $assetExporter, new NodeDataLoader($this->connection)), - 'Exporting node data' => new NodeDataToEventsProcessor($this->nodeTypeManager, $this->propertyMapper, $this->propertyConverter, $this->interDimensionalVariationGraph, $this->eventNormalizer, $filesystem, new NodeDataLoader($this->connection)), + 'Exporting node data' => new NodeDataToEventsProcessor($this->nodeTypeManager, $this->propertyMapper, $this->propertyConverter, $this->interDimensionalVariationGraph, $this->eventNormalizer, $filesystem, $this->rootNodeTypeMapping,new NodeDataLoader($this->connection)), 'Importing assets' => new AssetRepositoryImportProcessor($filesystem, $this->assetRepository, $this->resourceRepository, $this->resourceManager, $this->persistenceManager), 'Importing events' => new EventStoreImportProcessor(true, $filesystem, $this->eventStore, $this->eventNormalizer, $this->contentStreamId), ]; diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php index 67bd7df05c..1832917ae3 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php @@ -41,6 +41,7 @@ public function __construct( private readonly ResourceManager $resourceManager, private readonly PropertyMapper $propertyMapper, private readonly ContentStreamId $contentStreamId, + private readonly array $rootNodeTypeMapping, ) { } @@ -63,6 +64,7 @@ public function build( $serviceFactoryDependencies->propertyConverter, $serviceFactoryDependencies->eventStore, $this->contentStreamId, + $this->rootNodeTypeMapping, ); } } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index 9379206d5e..b87f3f9c6b 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -61,7 +61,6 @@ final class NodeDataToEventsProcessor implements ProcessorInterface * @var array<\Closure> */ private array $callbacks = []; - private NodeTypeName $sitesNodeTypeName; private WorkspaceName $workspaceName; private ContentStreamId $contentStreamId; private VisitedNodeAggregates $visitedNodes; @@ -90,9 +89,9 @@ public function __construct( private readonly InterDimensionalVariationGraph $interDimensionalVariationGraph, private readonly EventNormalizer $eventNormalizer, private readonly Filesystem $files, + private readonly array $rootNodeTypeMapping, private readonly iterable $nodeDataRows, ) { - $this->sitesNodeTypeName = NodeTypeNameFactory::forSites(); $this->contentStreamId = ContentStreamId::create(); $this->workspaceName = WorkspaceName::forLive(); $this->visitedNodes = new VisitedNodeAggregates(); @@ -103,18 +102,6 @@ public function setContentStreamId(ContentStreamId $contentStreamId): void $this->contentStreamId = $contentStreamId; } - public function setSitesNodeType(NodeTypeName $nodeTypeName): void - { - $nodeType = $this->nodeTypeManager->getNodeType($nodeTypeName); - if (!$nodeType?->isOfType(NodeTypeNameFactory::NAME_SITES)) { - throw new \InvalidArgumentException( - sprintf('Sites NodeType "%s" must be of type "%s"', $nodeTypeName->value, NodeTypeNameFactory::NAME_SITES), - 1695802415 - ); - } - $this->sitesNodeTypeName = $nodeTypeName; - } - public function onMessage(\Closure $callback): void { $this->callbacks[] = $callback; @@ -125,11 +112,14 @@ public function run(): ProcessorResult $this->resetRuntimeState(); foreach ($this->nodeDataRows as $nodeDataRow) { - if ($nodeDataRow['path'] === '/sites') { - $sitesNodeAggregateId = NodeAggregateId::fromString($nodeDataRow['identifier']); - $this->visitedNodes->addRootNode($sitesNodeAggregateId, $this->sitesNodeTypeName, NodePath::fromString('/sites'), $this->interDimensionalVariationGraph->getDimensionSpacePoints()); - $this->exportEvent(new RootNodeAggregateWithNodeWasCreated($this->workspaceName, $this->contentStreamId, $sitesNodeAggregateId, $this->sitesNodeTypeName, $this->interDimensionalVariationGraph->getDimensionSpacePoints(), NodeAggregateClassification::CLASSIFICATION_ROOT)); - continue; + if ($this->isRootNodePath($nodeDataRow['path'])) { + $rootNodeTypeName = $this->getRootNodeTypeByPath($nodeDataRow['path']); + if ($rootNodeTypeName) { + $rootNodeAggregateId = NodeAggregateId::fromString($nodeDataRow['identifier']); + $this->visitedNodes->addRootNode($rootNodeAggregateId, $rootNodeTypeName, NodePath::fromString('/sites'), $this->interDimensionalVariationGraph->getDimensionSpacePoints()); + $this->exportEvent(new RootNodeAggregateWithNodeWasCreated($this->workspaceName, $this->contentStreamId, $rootNodeAggregateId, $rootNodeTypeName, $this->interDimensionalVariationGraph->getDimensionSpacePoints(), NodeAggregateClassification::CLASSIFICATION_ROOT)); + continue; + } } if ($this->metaDataExported === false && $nodeDataRow['parentpath'] === '/sites') { $this->exportMetaData($nodeDataRow); @@ -535,7 +525,7 @@ private function isNodeHidden(array $nodeDataRow): bool && ( $hiddenBeforeDateTime == null || $hiddenBeforeDateTime > $now - || $hiddenBeforeDateTime<= $hiddenAfterDateTime + || $hiddenBeforeDateTime <= $hiddenAfterDateTime ) ) { return true; @@ -555,4 +545,14 @@ private function isNodeHidden(array $nodeDataRow): bool return false; } + + private function getRootNodeTypeByPath(string $path): ?NodeTypeName + { + return isset($this->rootNodeTypeMapping[$path]) ? NodeTypeName::fromString($this->rootNodeTypeMapping[$path]) : null; + } + + private function isRootNodePath(string $path): bool + { + return strpos($path, '/') === 0 && strpos($path, '/', 1) === false; + } } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Configuration/Settings.yaml b/Neos.ContentRepository.LegacyNodeMigration/Configuration/Settings.yaml new file mode 100644 index 0000000000..456ed3f206 --- /dev/null +++ b/Neos.ContentRepository.LegacyNodeMigration/Configuration/Settings.yaml @@ -0,0 +1,6 @@ +Neos: + ContentRepository: + LegacyNodeMigration: + rootNodeMapping: + default: + '/sites': 'Neos.Neos:Sites' diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index 7ab7f5a37a..ff9164be99 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -36,6 +36,7 @@ use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\ResourceManagement\PersistentResource; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; use PHPUnit\Framework\Assert; use PHPUnit\Framework\MockObject\Generator as MockGenerator; @@ -142,6 +143,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $this->currentContentRepository->getVariationGraph(), $this->getObject(EventNormalizer::class), $this->mockFilesystem, + ['/sites' => NodeTypeNameFactory::forSites()->value], $this->nodeDataRows ); if ($contentStream !== null) { From 351a19d3873da1c5b3454559d05ca5dafd8c574c Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 29 Oct 2024 17:15:34 +0100 Subject: [PATCH 2/5] FEATURE: Allow mapping of legacy root paths to RootNodeAggregate NodeTypeNames --- .../Classes/Command/CrCommandController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php index 365a9d5312..5cefcf49b9 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php @@ -50,7 +50,7 @@ public function __construct( private readonly SiteRepository $siteRepository, private readonly ProjectionReplayServiceFactory $projectionReplayServiceFactory, #[Flow\InjectConfiguration(path: "rootNodeMapping")] - private readonly array $rootNodeTypeMappingByContentRepository, + protected readonly array $rootNodeTypeMappingByContentRepository, ) { parent::__construct(); } From a3d2a319eb486d3bbbf5ff908f4c624b8299f313 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 1 Nov 2024 17:18:12 +0100 Subject: [PATCH 3/5] FEATURE: Allow mapping of legacy root paths to RootNodeAggregate NodeTypeNames --- .../Classes/Command/CrCommandController.php | 26 +++++++---- .../Classes/LegacyMigrationService.php | 2 +- .../Classes/LegacyMigrationServiceFactory.php | 2 +- .../Classes/NodeDataToEventsProcessor.php | 11 ++--- .../Classes/RootNodeTypeMapping.php | 30 ++++++++++++ .../Behavior/Bootstrap/FeatureContext.php | 19 ++++++-- .../Features/RootNodeTypeMapping.feature | 46 +++++++++++++++++++ 7 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php create mode 100644 Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/RootNodeTypeMapping.feature diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php index 5cefcf49b9..249908cc68 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/Command/CrCommandController.php @@ -22,10 +22,10 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\LegacyNodeMigration\LegacyMigrationService; use Neos\ContentRepository\LegacyNodeMigration\LegacyMigrationServiceFactory; +use Neos\ContentRepository\LegacyNodeMigration\RootNodeTypeMapping; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\ContentRepositoryRegistry\Factory\EventStore\DoctrineEventStoreFactory; use Neos\ContentRepositoryRegistry\Service\ProjectionReplayServiceFactory; -use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Flow\Property\PropertyMapper; @@ -35,6 +35,7 @@ use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\Domain\Model\Site; use Neos\Neos\Domain\Repository\SiteRepository; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; class CrCommandController extends CommandController { @@ -49,8 +50,6 @@ public function __construct( private readonly ContentRepositoryRegistry $contentRepositoryRegistry, private readonly SiteRepository $siteRepository, private readonly ProjectionReplayServiceFactory $projectionReplayServiceFactory, - #[Flow\InjectConfiguration(path: "rootNodeMapping")] - protected readonly array $rootNodeTypeMappingByContentRepository, ) { parent::__construct(); } @@ -59,7 +58,7 @@ public function __construct( * Migrate from the Legacy CR * * @param bool $verbose If set, all notices will be rendered - * @param string|null $config JSON encoded configuration, for example '{"dbal": {"dbname": "some-other-db"}, "resourcesPath": "/some/absolute/path"}' + * @param string|null $config JSON encoded configuration, for example '{"dbal": {"dbname": "some-other-db"}, "resourcesPath": "/some/absolute/path", "rootNodes": {"/sites": "Neos.Neos:Sites", "/other": "My.Package:SomeOtherRoot"}}' * @throws \Exception */ public function migrateLegacyDataCommand(bool $verbose = false, string $config = null): void @@ -71,6 +70,7 @@ public function migrateLegacyDataCommand(bool $verbose = false, string $config = throw new \InvalidArgumentException(sprintf('Failed to parse --config parameter: %s', $e->getMessage()), 1659526855, $e); } $resourcesPath = $parsedConfig['resourcesPath'] ?? self::defaultResourcesPath(); + $rootNodes = isset($parsedConfig['rootNodes']) ? RootNodeTypeMapping::fromArray($parsedConfig['rootNodes']) : $this->getDefaultRootNodes(); try { $connection = isset($parsedConfig['dbal']) ? DriverManager::getConnection(array_merge($this->connection->getParams(), $parsedConfig['dbal']), new Configuration()) : $this->connection; } catch (DBALException $e) { @@ -78,6 +78,7 @@ public function migrateLegacyDataCommand(bool $verbose = false, string $config = } } else { $resourcesPath = $this->determineResourcesPath(); + $rootNodes = $this->getDefaultRootNodes(); if (!$this->output->askConfirmation(sprintf('Do you want to migrate nodes from the current database "%s@%s" (y/n)? ', $this->connection->getParams()['dbname'] ?? '?', $this->connection->getParams()['host'] ?? '?'))) { $connection = $this->adjustDataBaseConnection($this->connection); } else { @@ -141,7 +142,7 @@ public function migrateLegacyDataCommand(bool $verbose = false, string $config = $this->resourceManager, $this->propertyMapper, $liveContentStreamId, - $this->rootNodeTypeMappingByContentRepository[$contentRepositoryId->value] ?? [], + $rootNodes, ) ); assert($legacyMigrationService instanceof LegacyMigrationService); @@ -162,14 +163,14 @@ private function adjustDataBaseConnection(Connection $connection): Connection { $connectionParams = $connection->getParams(); $connectionParams['driver'] = $this->output->select(sprintf('Driver? [%s] ', $connectionParams['driver'] ?? ''), ['pdo_mysql', 'pdo_sqlite', 'pdo_pgsql'], $connectionParams['driver'] ?? null); - $connectionParams['host'] = $this->output->ask(sprintf('Host? [%s] ',$connectionParams['host'] ?? ''), $connectionParams['host'] ?? null); - $port = $this->output->ask(sprintf('Port? [%s] ',$connectionParams['port'] ?? ''), isset($connectionParams['port']) ? (string)$connectionParams['port'] : null); + $connectionParams['host'] = $this->output->ask(sprintf('Host? [%s] ', $connectionParams['host'] ?? ''), $connectionParams['host'] ?? null); + $port = $this->output->ask(sprintf('Port? [%s] ', $connectionParams['port'] ?? ''), isset($connectionParams['port']) ? (string)$connectionParams['port'] : null); $connectionParams['port'] = isset($port) ? (int)$port : null; - $connectionParams['dbname'] = $this->output->ask(sprintf('DB name? [%s] ',$connectionParams['dbname'] ?? ''), $connectionParams['dbname'] ?? null); - $connectionParams['user'] = $this->output->ask(sprintf('DB user? [%s] ',$connectionParams['user'] ?? ''), $connectionParams['user'] ?? null); + $connectionParams['dbname'] = $this->output->ask(sprintf('DB name? [%s] ', $connectionParams['dbname'] ?? ''), $connectionParams['dbname'] ?? null); + $connectionParams['user'] = $this->output->ask(sprintf('DB user? [%s] ', $connectionParams['user'] ?? ''), $connectionParams['user'] ?? null); /** @phpstan-ignore-next-line */ $connectionParams['password'] = $this->output->askHiddenResponse(sprintf('DB password? [%s]', str_repeat('*', strlen($connectionParams['password'] ?? '')))) ?? $connectionParams['password']; - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ return DriverManager::getConnection($connectionParams, new Configuration()); } @@ -206,4 +207,9 @@ private static function defaultResourcesPath(): string { return FLOW_PATH_DATA . 'Persistent/Resources'; } + + private function getDefaultRootNodes(): RootNodeTypeMapping + { + return RootNodeTypeMapping::fromArray(['/sites' => NodeTypeNameFactory::NAME_SITES]); + } } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php index c4e6bb626e..f2ef4113a0 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationService.php @@ -58,7 +58,7 @@ public function __construct( private readonly PropertyConverter $propertyConverter, private readonly EventStoreInterface $eventStore, private readonly ContentStreamId $contentStreamId, - private readonly array $rootNodeTypeMapping + private readonly RootNodeTypeMapping $rootNodeTypeMapping ) { } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php index 1832917ae3..2010bc989e 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/LegacyMigrationServiceFactory.php @@ -41,7 +41,7 @@ public function __construct( private readonly ResourceManager $resourceManager, private readonly PropertyMapper $propertyMapper, private readonly ContentStreamId $contentStreamId, - private readonly array $rootNodeTypeMapping, + private readonly RootNodeTypeMapping $rootNodeTypeMapping, ) { } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index b87f3f9c6b..722c9aeef8 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -89,7 +89,7 @@ public function __construct( private readonly InterDimensionalVariationGraph $interDimensionalVariationGraph, private readonly EventNormalizer $eventNormalizer, private readonly Filesystem $files, - private readonly array $rootNodeTypeMapping, + private readonly RootNodeTypeMapping $rootNodeTypeMapping, private readonly iterable $nodeDataRows, ) { $this->contentStreamId = ContentStreamId::create(); @@ -113,10 +113,10 @@ public function run(): ProcessorResult foreach ($this->nodeDataRows as $nodeDataRow) { if ($this->isRootNodePath($nodeDataRow['path'])) { - $rootNodeTypeName = $this->getRootNodeTypeByPath($nodeDataRow['path']); + $rootNodeTypeName = $this->rootNodeTypeMapping->getByPath($nodeDataRow['path']); if ($rootNodeTypeName) { $rootNodeAggregateId = NodeAggregateId::fromString($nodeDataRow['identifier']); - $this->visitedNodes->addRootNode($rootNodeAggregateId, $rootNodeTypeName, NodePath::fromString('/sites'), $this->interDimensionalVariationGraph->getDimensionSpacePoints()); + $this->visitedNodes->addRootNode($rootNodeAggregateId, $rootNodeTypeName, NodePath::fromString($nodeDataRow['path']), $this->interDimensionalVariationGraph->getDimensionSpacePoints()); $this->exportEvent(new RootNodeAggregateWithNodeWasCreated($this->workspaceName, $this->contentStreamId, $rootNodeAggregateId, $rootNodeTypeName, $this->interDimensionalVariationGraph->getDimensionSpacePoints(), NodeAggregateClassification::CLASSIFICATION_ROOT)); continue; } @@ -546,11 +546,6 @@ private function isNodeHidden(array $nodeDataRow): bool } - private function getRootNodeTypeByPath(string $path): ?NodeTypeName - { - return isset($this->rootNodeTypeMapping[$path]) ? NodeTypeName::fromString($this->rootNodeTypeMapping[$path]) : null; - } - private function isRootNodePath(string $path): bool { return strpos($path, '/') === 0 && strpos($path, '/', 1) === false; diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php new file mode 100644 index 0000000000..c39905bdc0 --- /dev/null +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php @@ -0,0 +1,30 @@ + $mapping + */ + private function __construct( + public readonly array $mapping, + ) { + } + + /** + * @param array $mapping + * @return self + */ + public static function fromArray(array $mapping): self + { + return new self($mapping); + } + + public function getByPath(string $path): ?NodeTypeName + { + return isset($this->mapping[$path]) ? NodeTypeName::fromString($this->mapping[$path]) : null; + } +} \ No newline at end of file diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index ff9164be99..d73bf80096 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -32,6 +32,7 @@ use Neos\ContentRepository\Export\Severity; use Neos\ContentRepository\LegacyNodeMigration\NodeDataToAssetsProcessor; use Neos\ContentRepository\LegacyNodeMigration\NodeDataToEventsProcessor; +use Neos\ContentRepository\LegacyNodeMigration\RootNodeTypeMapping; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteTrait; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Property\PropertyMapper; @@ -107,18 +108,28 @@ public function iHaveTheFollowingNodeDataRows(TableNode $nodeDataRows): void 'properties' => !empty($row['Properties']) ? $row['Properties'] : '{}', 'dimensionvalues' => !empty($row['Dimension Values']) ? $row['Dimension Values'] : '{}', 'hiddeninindex' => $row['Hidden in index'] ?? '0', - 'hiddenbeforedatetime' => !empty($row['Hidden before DateTime']) ? ($row['Hidden before DateTime']): null, - 'hiddenafterdatetime' => !empty($row['Hidden after DateTime']) ? ($row['Hidden after DateTime']) : null, + 'hiddenbeforedatetime' => !empty($row['Hidden before DateTime']) ? ($row['Hidden before DateTime']) : null, + 'hiddenafterdatetime' => !empty($row['Hidden after DateTime']) ? ($row['Hidden after DateTime']) : null, 'hidden' => $row['Hidden'] ?? '0', ]; }, $nodeDataRows->getHash()); } + /** + * @When /^I run the event migration for content stream (.*) with rootNode mapping (.*)$/ + */ + public function iRunTheEventMigrationForContentStreamWithRootnodeMapping(string $contentStream = null, string $rootNodeMapping): void + { + $contentStream = trim($contentStream, '"'); + $rootNodeTypeMapping = RootNodeTypeMapping::fromArray(json_decode($rootNodeMapping, true)); + $this->iRunTheEventMigration($contentStream, $rootNodeTypeMapping); + } + /** * @When I run the event migration * @When I run the event migration for content stream :contentStream */ - public function iRunTheEventMigration(string $contentStream = null): void + public function iRunTheEventMigration(string $contentStream = null, RootNodeTypeMapping $rootNodeTypeMapping = null): void { $nodeTypeManager = $this->currentContentRepository->getNodeTypeManager(); $propertyMapper = $this->getObject(PropertyMapper::class); @@ -143,7 +154,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $this->currentContentRepository->getVariationGraph(), $this->getObject(EventNormalizer::class), $this->mockFilesystem, - ['/sites' => NodeTypeNameFactory::forSites()->value], + $rootNodeTypeMapping ?? RootNodeTypeMapping::fromArray(['/sites' => NodeTypeNameFactory::NAME_SITES]), $this->nodeDataRows ); if ($contentStream !== null) { diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/RootNodeTypeMapping.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/RootNodeTypeMapping.feature new file mode 100644 index 0000000000..2834b27a23 --- /dev/null +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/RootNodeTypeMapping.feature @@ -0,0 +1,46 @@ +@contentrepository +Feature: Simple migrations without content dimensions but other root nodetype name + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.Neos:Site': {} + 'Some.Package:Homepage': + superTypes: + 'Neos.Neos:Site': true + properties: + 'text': + type: string + defaultValue: 'My default text' + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + + Scenario: Migration without rootNodeType configuration for all root nodes + When I have the following node data rows: + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"text": "foo"} | + | test-root-node-id | /test | unstructured | | + | test-node-id | /test/test-site | Some.Package:Homepage | {"text": "foo"} | + And I run the event migration for content stream "cs-id" + Then I expect the following errors to be logged + | Failed to find parent node for node with id "test-root-node-id" and dimensions: []. Please ensure that the new content repository has a valid content dimension configuration. Also note that the old CR can sometimes have orphaned nodes. | + | Failed to find parent node for node with id "test-node-id" and dimensions: []. Please ensure that the new content repository has a valid content dimension configuration. Also note that the old CR can sometimes have orphaned nodes. | + + + Scenario: Migration with rootNodeType configuration for all root nodes + When I have the following node data rows: + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"text": "foo"} | + | test-root-node-id | /test | unstructured | | + | test-node-id | /test/test-site | Some.Package:Homepage | {"text": "foo"} | + And I run the event migration for content stream "cs-id" with rootNode mapping {"/sites": "Neos.Neos:Sites", "/test": "Neos.ContentRepository.LegacyNodeMigration:TestRoot"} + Then I expect the following events to be exported + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {"contentStreamId": "cs-id", "nodeAggregateId": "sites-node-id", "nodeTypeName": "Neos.Neos:Sites", "nodeAggregateClassification": "root"} | + | NodeAggregateWithNodeWasCreated | {"contentStreamId": "cs-id", "nodeAggregateId": "site-node-id", "nodeTypeName": "Some.Package:Homepage", "nodeName": "test-site", "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular", "initialPropertyValues": {"text": {"type": "string", "value": "foo"}}} | + | RootNodeAggregateWithNodeWasCreated | {"contentStreamId": "cs-id", "nodeAggregateId": "test-root-node-id", "nodeTypeName": "Neos.ContentRepository.LegacyNodeMigration:TestRoot", "nodeAggregateClassification": "root"} | + | NodeAggregateWithNodeWasCreated | {"contentStreamId": "cs-id", "nodeAggregateId": "test-node-id", "nodeTypeName": "Some.Package:Homepage", "nodeName": "test-site", "parentNodeAggregateId": "test-root-node-id", "nodeAggregateClassification": "regular", "initialPropertyValues": {"text": {"type": "string", "value": "foo"}}} | From ce2603896b24de961471bf9e1b08e180691bcc65 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 1 Nov 2024 17:19:36 +0100 Subject: [PATCH 4/5] FEATURE: Allow mapping of legacy root paths to RootNodeAggregate NodeTypeNames --- .../Classes/RootNodeTypeMapping.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php index c39905bdc0..8235838389 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php @@ -1,4 +1,5 @@ Date: Fri, 1 Nov 2024 17:27:33 +0100 Subject: [PATCH 5/5] FEATURE: Allow mapping of legacy root paths to RootNodeAggregate NodeTypeNames --- .../Classes/RootNodeTypeMapping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php index 8235838389..5b3bf28910 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/RootNodeTypeMapping.php @@ -28,4 +28,4 @@ public function getByPath(string $path): ?NodeTypeName { return isset($this->mapping[$path]) ? NodeTypeName::fromString($this->mapping[$path]) : null; } -} \ No newline at end of file +}