diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php
index ba5440ffc16..8d256106bc8 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php
+++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php
@@ -46,14 +46,13 @@
use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint;
use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePointSet;
-use Neos\ContentRepository\Core\SharedModel\Node\PropertyName;
use Neos\ContentRepository\Core\NodeType\NodeType;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
-use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\Flow\Persistence\Doctrine\DataTypes\JsonArrayType;
use Neos\Flow\Property\PropertyMapper;
+use Neos\Neos\Domain\Service\NodeTypeNameFactory;
use Ramsey\Uuid\Uuid;
use Webmozart\Assert\Assert;
@@ -88,7 +87,7 @@ public function __construct(
private readonly Filesystem $files,
private readonly iterable $nodeDataRows,
) {
- $this->sitesNodeTypeName = NodeTypeName::fromString('Neos.Neos:Sites');
+ $this->sitesNodeTypeName = NodeTypeNameFactory::forSites();
$this->contentStreamId = ContentStreamId::create();
$this->visitedNodes = new VisitedNodeAggregates();
}
@@ -100,6 +99,13 @@ public function setContentStreamId(ContentStreamId $contentStreamId): void
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;
}
@@ -244,6 +250,13 @@ public function processNodeDataWithoutFallbackToEmptyDimension(NodeAggregateId $
$nodeType = $this->nodeTypeManager->getNodeType($nodeTypeName);
$serializedPropertyValuesAndReferences = $this->extractPropertyValuesAndReferences($nodeDataRow, $nodeType);
+ $isSiteNode = $nodeDataRow['parentpath'] === '/sites';
+ if ($isSiteNode && !$nodeType->isOfType(NodeTypeNameFactory::NAME_SITE)) {
+ throw new MigrationException(sprintf(
+ 'The site node "%s" (type: "%s") must be of type "%s"', $nodeDataRow['identifier'], $nodeTypeName->value, NodeTypeNameFactory::NAME_SITE
+ ), 1695801620);
+ }
+
if ($this->isAutoCreatedChildNode($parentNodeAggregate->nodeTypeName, $nodeName) && !$this->visitedNodes->containsNodeAggregate($nodeAggregateId)) {
// Create tethered node if the node was not found before.
// If the node was already visited, we want to create a node variant (and keep the tethering status)
diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature
index 220bda3b208..bc5cf9671ea 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature
+++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature
@@ -8,7 +8,10 @@ Feature: Export of used Assets, Image Variants and Persistent Resources
And using the following node types:
"""yaml
'unstructured': {}
- 'Some.Package:SomeNodeType':
+ 'Neos.Neos:Site': {}
+ 'Some.Package:Homepage':
+ superTypes:
+ 'Neos.Neos:Site': true
properties:
'string':
type: string
@@ -43,7 +46,7 @@ Feature: Export of used Assets, Image Variants and Persistent Resources
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:SomeNodeType | {"string": "asset:\/\/variant1"} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/variant1"} |
And I run the asset migration
Then I expect the following Assets to be exported:
"""
@@ -87,10 +90,10 @@ Feature: Export of used Assets, Image Variants and Persistent Resources
When I have the following node data rows:
| Identifier | Path | Node Type | Dimension Values | Properties |
| sites-node-id | /sites | unstructured | | |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | | {"string": "asset:\/\/asset1"} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["ch"]} | {"image": {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["en"]} | {"assets": [{"__flow_object_type": "Neos\\Media\\Domain\\Model\\Document", "__identifier": "asset3"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\ImageVariant", "__identifier": "variant1"}]} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["de"]} | {"string": "some text with an asset link"} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | | {"string": "asset:\/\/asset1"} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} | {"image": {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["en"]} | {"assets": [{"__flow_object_type": "Neos\\Media\\Domain\\Model\\Document", "__identifier": "asset3"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\ImageVariant", "__identifier": "variant1"}]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} | {"string": "some text with an asset link"} |
And I run the asset migration
Then I expect the following Assets to be exported:
"""
@@ -178,10 +181,10 @@ Feature: Export of used Assets, Image Variants and Persistent Resources
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:SomeNodeType | {"string": "asset:\/\/non-existing-asset"} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/non-existing-asset"} |
And I run the asset migration
Then I expect no Assets to be exported
And I expect no ImageVariants to be exported
And I expect no PersistentResources to be exported
And I expect the following errors to be logged
- | Failed to extract assets of property "string" of node "site-node-id" (type: "Some.Package:SomeNodeType"): Failed to find mock asset with id "non-existing-asset" |
+ | Failed to extract assets of property "string" of node "site-node-id" (type: "Some.Package:Homepage"): Failed to find mock asset with id "non-existing-asset" |
diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Basic.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Basic.feature
index 20462bc12b0..3623e2fe61a 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Basic.feature
+++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Basic.feature
@@ -5,7 +5,11 @@ Feature: Simple migrations without content dimensions
Given using no content dimensions
And using the following node types:
"""yaml
- 'Some.Package:SomeNodeType':
+ 'unstructured': {}
+ 'Neos.Neos:Site': {}
+ 'Some.Package:Homepage':
+ superTypes:
+ 'Neos.Neos:Site': true
properties:
'text':
type: string
@@ -18,9 +22,9 @@ Feature: Simple migrations without content dimensions
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:SomeNodeType | {"text": "foo"} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"text": "foo"} |
And I run the event migration for content stream "cs-id"
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:SomeNodeType", "nodeName": "test-site", "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular", "initialPropertyValues": {"text": {"type": "string", "value": "foo"}}} |
+ | 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"}}} |
diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Errors.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Errors.feature
index c036cb2bf52..4cf57e96a4c 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Errors.feature
+++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Errors.feature
@@ -5,13 +5,22 @@ Feature: Exceptional cases during migrations
Given using no content dimensions
And using the following node types:
"""yaml
- 'unstructured': []
- 'Some.Package:SomeNodeType':
+ 'unstructured': {}
+ 'Neos.Neos:Site': {}
+ 'Some.Package:Homepage':
+ superTypes:
+ 'Neos.Neos:Site': true
properties:
'text':
type: string
defaultValue: 'My default text'
- 'Some.Package:SomeOtherNodeType': []
+ 'Some.Package:SomeNodeType':
+ properties:
+ 'text':
+ type: string
+ 'Some.Package:SomeOtherHomepage':
+ superTypes:
+ 'Neos.Neos:Site': true
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
@@ -23,26 +32,26 @@ Feature: Exceptional cases during migrations
When I have the following node data rows:
| Identifier | Path | Node Type | Dimension Values |
| sites-node-id | /sites | unstructured | |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["de"]} |
- | site-node-id | /sites/test-site | Some.Package:SomeOtherNodeType | {"language": ["en"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
+ | site-node-id | /sites/test-site | Some.Package:SomeOtherHomepage | {"language": ["en"]} |
And I run the event migration
Then I expect a MigrationError with the message
"""
- Node aggregate with id "site-node-id" has a type of "Some.Package:SomeOtherNodeType" in content dimension [{"language":"en"}]. I was visited previously for content dimension [{"language":"de"}] with the type "Some.Package:SomeNodeType". Node variants must not have different types
+ Node aggregate with id "site-node-id" has a type of "Some.Package:SomeOtherHomepage" in content dimension [{"language":"en"}]. I was visited previously for content dimension [{"language":"de"}] with the type "Some.Package:Homepage". Node variants must not have different types
"""
# Note: The behavior was changed with https://github.com/neos/neos-development-collection/pull/4201
Scenario: Node with missing parent
When I have the following node data rows:
- | Identifier | Path |
- | sites | /sites |
- | a | /sites/a |
- | c | /sites/b/c |
+ | Identifier | Path | Node Type |
+ | sites | /sites | unstructured |
+ | a | /sites/a | Some.Package:Homepage |
+ | c | /sites/b/c | unstructured |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
| RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites", "nodeTypeName": "Neos.Neos:Sites", "nodeAggregateClassification": "root"} |
- | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "sites"} |
+ | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "nodeTypeName": "Some.Package:Homepage", "parentNodeAggregateId": "sites"} |
And I expect the following errors to be logged
| Failed to find parent node for node with id "c" 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. |
@@ -50,17 +59,17 @@ Feature: Exceptional cases during migrations
# Note: The behavior was changed with https://github.com/neos/neos-development-collection/pull/4201
Scenario: Nodes out of order
When I have the following node data rows:
- | Identifier | Path |
- | sites | /sites |
- | a | /sites/a |
- | c | /sites/b/c |
- | b | /sites/b |
+ | Identifier | Path | Node Type |
+ | sites | /sites | unstructured |
+ | a | /sites/a | Some.Package:Homepage |
+ | c | /sites/b/c | unstructured |
+ | b | /sites/b | Some.Package:Homepage |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
| RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites", "nodeTypeName": "Neos.Neos:Sites", "nodeAggregateClassification": "root"} |
- | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "sites"} |
- | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "parentNodeAggregateId": "sites"} |
+ | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "nodeTypeName": "Some.Package:Homepage", "parentNodeAggregateId": "sites"} |
+ | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "nodeTypeName": "Some.Package:Homepage", "parentNodeAggregateId": "sites"} |
And I expect the following errors to be logged
| Failed to find parent node for node with id "c" 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. |
@@ -104,11 +113,11 @@ Feature: Exceptional cases during migrations
| Identifier | Default | Values | Generalizations |
| language | en | en, de, ch | ch->de |
When I have the following node data rows:
- | Identifier | Path | Dimension Values |
- | sites-node-id | /sites | |
- | site-node-id | /sites/test-site | {"language": ["de"]} |
- | site-node-id | /sites/test-site | {"language": ["ch"]} |
- | site-node-id | /sites/test-site | {"language": ["ch"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites-node-id | /sites | unstructured | |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} |
And I run the event migration
Then I expect a MigrationError with the message
"""
@@ -120,12 +129,23 @@ Feature: Exceptional cases during migrations
| Identifier | Default | Values | Generalizations |
| language | en | en, de, ch | ch->de |
When I have the following node data rows:
- | Identifier | Path | Dimension Values |
- | sites-node-id | /sites | |
- | site-node-id | /sites/test-site | {"language": ["de"]} |
- | site-node-id | /sites/test-site | {"language": ["de"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites-node-id | /sites | unstructured | |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
And I run the event migration
Then I expect a MigrationError with the message
"""
Node "site-node-id" for dimension {"language":"de"} was already created previously
"""
+
+ Scenario: Homepage node is not of type "Neos.Neos:Site"
+ When I have the following node data rows:
+ | Identifier | Path | Node Type |
+ | sites-node-id | /sites | unstructured |
+ | site-node-id | /sites/test-site | unstructured |
+ And I run the event migration
+ Then I expect a MigrationError with the message
+ """
+ The site node "site-node-id" (type: "unstructured") must be of type "Neos.Neos:Site"
+ """
diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature
index e9dcb7dfc60..7c67812eb50 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature
+++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature
@@ -5,7 +5,11 @@ Feature: Migrations that contain nodes with "reference" or "references propertie
Given using no content dimensions
And using the following node types:
"""yaml
- 'Some.Package:Homepage': []
+ 'unstructured': {}
+ 'Neos.Neos:Site': {}
+ 'Some.Package:Homepage':
+ superTypes:
+ 'Neos.Neos:Site': true
'Some.Package:SomeNodeType':
properties:
'text':
diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature
index f5a1299ec0b..e6285a44323 100644
--- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature
+++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature
@@ -8,27 +8,26 @@ Feature: Migrating nodes with content dimensions
And using the following node types:
"""yaml
'unstructured': {}
- 'Some.Package:SomeNodeType':
- properties:
- 'text':
- type: string
- defaultValue: 'My default text'
+ 'Neos.Neos:Site': {}
+ 'Some.Package:Homepage':
+ superTypes:
+ 'Neos.Neos:Site': true
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
Scenario: Node specialization variants are prioritized over peer variants
When I have the following node data rows:
- | Identifier | Path | Node Type | Dimension Values |
- | sites-node-id | /sites | unstructured | |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["de"]} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["en"]} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["ch"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites-node-id | /sites | unstructured | |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["en"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
| RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites-node-id", "nodeTypeName": "Neos.Neos:Sites", "coveredDimensionSpacePoints": [{"language": "en"},{"language": "de"},{"language": "ch"}], "nodeAggregateClassification": "root"} |
- | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site-node-id", "nodeTypeName": "Some.Package:SomeNodeType", "nodeName": "test-site", "originDimensionSpacePoint": {"language": "de"}, "coveredDimensionSpacePoints": [{"language": "de"},{"language": "ch"}], "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular"} |
+ | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site-node-id", "nodeTypeName": "Some.Package:Homepage", "nodeName": "test-site", "originDimensionSpacePoint": {"language": "de"}, "coveredDimensionSpacePoints": [{"language": "de"},{"language": "ch"}], "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular"} |
| NodePeerVariantWasCreated | {"nodeAggregateId": "site-node-id", "sourceOrigin": {"language": "de"}, "peerOrigin": {"language": "en"}, "peerCoverage": [{"language": "en"}]} |
| NodeSpecializationVariantWasCreated | {"nodeAggregateId": "site-node-id", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationCoverage": [{"language": "ch"}]} |
@@ -36,14 +35,14 @@ Feature: Migrating nodes with content dimensions
When I have the following node data rows:
| Identifier | Path | Node Type | Dimension Values |
| sites-node-id | /sites | unstructured | |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["ch"]} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["en"]} |
- | site-node-id | /sites/test-site | Some.Package:SomeNodeType | {"language": ["de"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["en"]} |
+ | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
| RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites-node-id", "nodeTypeName": "Neos.Neos:Sites", "coveredDimensionSpacePoints": [{"language": "en"},{"language": "de"},{"language": "ch"}], "nodeAggregateClassification": "root"} |
- | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site-node-id", "nodeTypeName": "Some.Package:SomeNodeType", "nodeName": "test-site", "originDimensionSpacePoint": {"language": "ch"}, "coveredDimensionSpacePoints": [{"language": "ch"}], "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular"} |
+ | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site-node-id", "nodeTypeName": "Some.Package:Homepage", "nodeName": "test-site", "originDimensionSpacePoint": {"language": "ch"}, "coveredDimensionSpacePoints": [{"language": "ch"}], "parentNodeAggregateId": "sites-node-id", "nodeAggregateClassification": "regular"} |
| NodePeerVariantWasCreated | {"nodeAggregateId": "site-node-id", "sourceOrigin": {"language": "ch"}, "peerOrigin": {"language": "en"}, "peerCoverage": [{"language": "en"}]} |
| NodeGeneralizationVariantWasCreated | {"nodeAggregateId": "site-node-id", "sourceOrigin": {"language": "ch"}, "generalizationOrigin": {"language": "de"}, "generalizationCoverage": [{"language": "de"}]} |
@@ -52,10 +51,10 @@ Feature: Migrating nodes with content dimensions
| Identifier | Default | Values | Generalizations |
| language | mul | mul, en, de, ch | ch->de->mul |
When I have the following node data rows:
- | Identifier | Path | Dimension Values |
- | sites | /sites | |
- | site | /sites/site | {"language": ["mul"]} |
- | site | /sites/site | {"language": ["de"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites | /sites | unstructured | |
+ | site | /sites/site | Some.Package:Homepage | {"language": ["mul"]} |
+ | site | /sites/site | Some.Package:Homepage | {"language": ["de"]} |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
@@ -65,13 +64,13 @@ Feature: Migrating nodes with content dimensions
Scenario: Node variant with different parent node (moved)
When I have the following node data rows:
- | Identifier | Path | Dimension Values |
- | sites | /sites | |
- | site | /sites/site | {"language": ["de"]} |
- | a | /sites/site/a | {"language": ["de"]} |
- | a1 | /sites/site/a/a1 | {"language": ["de"]} |
- | b | /sites/site/b | {"language": ["de"]} |
- | a1 | /sites/site/b/a1 | {"language": ["ch"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites | /sites | unstructured | |
+ | site | /sites/site | Some.Package:Homepage | {"language": ["de"]} |
+ | a | /sites/site/a | unstructured | {"language": ["de"]} |
+ | a1 | /sites/site/a/a1 | unstructured | {"language": ["de"]} |
+ | b | /sites/site/b | unstructured | {"language": ["de"]} |
+ | a1 | /sites/site/b/a1 | unstructured | {"language": ["ch"]} |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
@@ -86,14 +85,14 @@ Feature: Migrating nodes with content dimensions
Scenario: Node variant with different grand parent node (ancestor node was moved) - Note: There is only NodeAggregateWasMoved event for "a" and not for "a1"
When I have the following node data rows:
- | Identifier | Path | Dimension Values |
- | sites | /sites | |
- | site | /sites/site | {"language": ["de"]} |
- | a | /sites/site/a | {"language": ["de"]} |
- | a1 | /sites/site/a/a1 | {"language": ["de"]} |
- | b | /sites/site/b | {"language": ["de"]} |
- | a | /sites/site/b/a | {"language": ["ch"]} |
- | a1 | /sites/site/b/a/a1 | {"language": ["ch"]} |
+ | Identifier | Path | Node Type | Dimension Values |
+ | sites | /sites | unstructured | |
+ | site | /sites/site | Some.Package:Homepage | {"language": ["de"]} |
+ | a | /sites/site/a | unstructured | {"language": ["de"]} |
+ | a1 | /sites/site/a/a1 | unstructured | {"language": ["de"]} |
+ | b | /sites/site/b | unstructured | {"language": ["de"]} |
+ | a | /sites/site/b/a | unstructured | {"language": ["ch"]} |
+ | a1 | /sites/site/b/a/a1 | unstructured | {"language": ["ch"]} |
And I run the event migration
Then I expect the following events to be exported
| Type | Payload |
diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ParentsOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ParentsOperation.php
index dcf0d4233c3..2310ee2d59f 100644
--- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ParentsOperation.php
+++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ParentsOperation.php
@@ -61,7 +61,6 @@ public function canEvaluate($context)
*
* @param FlowQuery $flowQuery the FlowQuery object
* @param array $arguments the arguments for this operation
- * @todo Compare to node type Neos.Neos:Site instead of path once it is available
* @return void
*/
public function evaluate(FlowQuery $flowQuery, array $arguments)
diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php
index eb3dab88e26..6a1c6b924fe 100644
--- a/Neos.Media.Browser/Classes/Controller/UsageController.php
+++ b/Neos.Media.Browser/Classes/Controller/UsageController.php
@@ -12,20 +12,21 @@
* source code.
*/
+use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter;
+use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
-use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Media\Domain\Model\AssetInterface;
use Neos\Media\Domain\Service\AssetService;
use Neos\Neos\Domain\Repository\SiteRepository;
+use Neos\Neos\Domain\Service\NodeTypeNameFactory;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Neos\Neos\Service\UserService;
use Neos\Neos\Domain\Service\UserService as DomainUserService;
use Neos\Neos\AssetUsage\Dto\AssetUsageReference;
-use Neos\Neos\Domain\Service\SiteNodeUtility;
use Neos\Neos\Domain\Model\Site;
/**
@@ -65,12 +66,6 @@ class UsageController extends ActionController
*/
protected $domainUserService;
- /**
- * @Flow\Inject
- * @var SiteNodeUtility
- */
- protected $siteNodeUtility;
-
/**
* Get Related Nodes for an asset
*
@@ -126,28 +121,32 @@ public function relatedNodesAction(AssetInterface $asset)
continue;
}
- $node = $contentRepository->getContentGraph()->findNodeByIdAndOriginDimensionSpacePoint(
+ $subgraph = $contentRepository->getContentGraph()->getSubgraph(
$usage->getContentStreamId(),
- $usage->getNodeAggregateId(),
- $usage->getOriginDimensionSpacePoint()
+ $usage->getOriginDimensionSpacePoint()->toDimensionSpacePoint(),
+ VisibilityConstraints::withoutRestrictions()
);
+ $node = $subgraph->findNodeById($usage->getNodeAggregateId());
// this should actually never happen.
if (!$node) {
$inaccessibleRelations[] = $inaccessibleRelation;
continue;
}
- $flowQuery = new FlowQuery([$node]);
- $documentNode = $flowQuery->closest('[instanceof Neos.Neos:Document]')->get(0);
+ $documentNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_DOCUMENT));
// this should actually never happen, too.
if (!$documentNode) {
$inaccessibleRelations[] = $inaccessibleRelation;
continue;
}
-
- $siteNode = $this->siteNodeUtility->findSiteNode($node);
+ $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE));
+ // this should actually never happen, too. :D
+ if (!$siteNode) {
+ $inaccessibleRelations[] = $inaccessibleRelation;
+ continue;
+ }
foreach ($existingSites as $existingSite) {
/** @var Site $existingSite * */
if ($siteNode->nodeName->equals($existingSite->getNodeName()->toNodeName())) {
diff --git a/Neos.Neos/Classes/Command/SiteCommandController.php b/Neos.Neos/Classes/Command/SiteCommandController.php
index 5de738b7e24..14354cd743b 100644
--- a/Neos.Neos/Classes/Command/SiteCommandController.php
+++ b/Neos.Neos/Classes/Command/SiteCommandController.php
@@ -95,7 +95,7 @@ public function createCommand($name, $packageKey, $nodeType, $nodeName = null, $
} catch (SiteNodeTypeIsInvalid $exception) {
$this->outputLine(
'The given node type "%s" is not based on the superType "%s"',
- [$nodeType, NodeTypeNameFactory::forSite()]
+ [$nodeType, NodeTypeNameFactory::NAME_SITE]
);
$this->quit(1);
} catch (SiteNodeNameIsAlreadyInUseByAnotherSite | NodeNameIsAlreadyOccupied $exception) {
diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php
index 75d8f5b4a9c..04fecdde89d 100755
--- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php
+++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php
@@ -398,7 +398,7 @@ public function createSiteNodeAction($packageKey, $siteName, $nodeType)
$this->addFlashMessage(
$this->getModuleLabel(
'sites.siteCreationError.givenNodeTypeNotBasedOnSuperType.body',
- [$nodeType, NodeTypeNameFactory::forSite()]
+ [$nodeType, NodeTypeNameFactory::NAME_SITE]
),
$this->getModuleLabel('sites.siteCreationError.givenNodeTypeNotBasedOnSuperType.title'),
Message::SEVERITY_ERROR,
diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
index 34ba8e99ea5..682c71b212e 100644
--- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
+++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php
@@ -798,7 +798,8 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos
if (is_null($documentNode)) {
$documentNode = $ancestor;
}
- // the site node is the last ancestor of type Document
+ }
+ if ($this->getNodeType($ancestor)->isOfType(NodeTypeNameFactory::NAME_SITE)) {
$siteNode = $documentNode;
}
}
diff --git a/Neos.Neos/Classes/Domain/Exception/CurrentUserIsMissing.php b/Neos.Neos/Classes/Domain/Exception/CurrentUserIsMissing.php
deleted file mode 100644
index 17ee064d9c3..00000000000
--- a/Neos.Neos/Classes/Domain/Exception/CurrentUserIsMissing.php
+++ /dev/null
@@ -1,29 +0,0 @@
-value
- . '" is not of required type "' . NodeTypeNameFactory::forSite()->value . '"',
+ . '" is not of required type "' . NodeTypeNameFactory::NAME_SITE . '"',
1412372375
);
}
diff --git a/Neos.Neos/Classes/Domain/Exception/SitesNodeIsMissing.php b/Neos.Neos/Classes/Domain/Exception/SitesNodeIsMissing.php
deleted file mode 100644
index ee2cea42256..00000000000
--- a/Neos.Neos/Classes/Domain/Exception/SitesNodeIsMissing.php
+++ /dev/null
@@ -1,30 +0,0 @@
-value . '" root node is missing.',
- 1651956364
- );
- }
-}
diff --git a/Neos.Neos/Classes/Domain/Repository/SiteRepository.php b/Neos.Neos/Classes/Domain/Repository/SiteRepository.php
index 815d9057028..d097041e88d 100644
--- a/Neos.Neos/Classes/Domain/Repository/SiteRepository.php
+++ b/Neos.Neos/Classes/Domain/Repository/SiteRepository.php
@@ -15,13 +15,17 @@
namespace Neos\Neos\Domain\Repository;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
+use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\QueryInterface;
use Neos\Flow\Persistence\QueryResultInterface;
use Neos\Flow\Persistence\Repository;
-use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Exception as NeosException;
+use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Model\SiteNodeName;
+use Neos\Neos\Domain\Service\NodeTypeNameFactory;
+use Neos\Neos\Domain\Service\SiteNodeUtility;
+use Neos\Neos\Utility\NodeTypeWithFallbackProvider;
/**
* The Site Repository
@@ -33,6 +37,11 @@
*/
class SiteRepository extends Repository
{
+ use NodeTypeWithFallbackProvider;
+
+ #[Flow\Inject]
+ protected ContentRepositoryRegistry $contentRepositoryRegistry;
+
/**
* @var array
*/
@@ -95,8 +104,26 @@ public function findOneByNodeName(string|SiteNodeName $nodeName): ?Site
return $site;
}
+ /**
+ * Finds a given site by site node.
+ *
+ * To find the correct site node by its descended child node leverage `findClosestNode`:
+ * ```php
+ * $siteNode = $subgraph->findClosestNode(
+ * $node->nodeAggregateId,
+ * FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE)
+ * );
+ * ```
+ *
+ * To resolve the SiteNode by a Site use {@see SiteNodeUtility::findSiteNodeBySite()}
+ *
+ * @throws \Neos\Neos\Domain\Exception in case the passed $siteNode is not a real site node or no site matches this site node.
+ */
public function findSiteBySiteNode(Node $siteNode): Site
{
+ if (!$this->getNodeType($siteNode)->isOfType(NodeTypeNameFactory::NAME_SITE)) {
+ throw new \Neos\Neos\Domain\Exception(sprintf('Node %s is not a site node. Site nodes must be of type "%s".', $siteNode->nodeAggregateId->value, NodeTypeNameFactory::NAME_SITE), 1697108987);
+ }
if ($siteNode->nodeName === null) {
throw new \Neos\Neos\Domain\Exception(sprintf('Site node "%s" is unnamed', $siteNode->nodeAggregateId->value), 1681286146);
}
diff --git a/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php b/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php
index 49aa0564179..5fd9c00d611 100644
--- a/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php
+++ b/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php
@@ -15,21 +15,20 @@
namespace Neos\Neos\Domain\Service;
use Neos\ContentRepository\Core\Factory\ContentRepositoryId;
+use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
-use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
-use Neos\Neos\FrontendRouting\NodeAddress;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
+use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
-use Neos\Neos\Utility\NodeTypeWithFallbackProvider;
+use Neos\Neos\FrontendRouting\NodeAddress;
#[Flow\Scope('singleton')]
class NodeSiteResolvingService
{
- use NodeTypeWithFallbackProvider;
-
#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;
+ /** @internal */
public function findSiteNodeForNodeAddress(
NodeAddress $nodeAddress,
ContentRepositoryId $contentRepositoryId
@@ -44,20 +43,13 @@ public function findSiteNodeForNodeAddress(
? VisibilityConstraints::frontend()
: VisibilityConstraints::withoutRestrictions()
);
+
$node = $subgraph->findNodeById($nodeAddress->nodeAggregateId);
- if (is_null($node)) {
+ if (!$node) {
return null;
}
- $previousNode = null;
- do {
- if ($this->getNodeType($node)->isOfType(NodeTypeNameFactory::NAME_SITES)) {
- // the Site node is the one level underneath the "Sites" node.
- return $previousNode;
- }
- $previousNode = $node;
- } while ($node = $subgraph->findParentNode($node->nodeAggregateId));
-
- // no Site node found at rootline
- return null;
+ $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE));
+
+ return $siteNode;
}
}
diff --git a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php
index b24f43096c1..9f5e7b9a4ff 100644
--- a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php
+++ b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php
@@ -16,14 +16,12 @@
namespace Neos\Neos\Domain\Service;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
-use Neos\ContentRepository\Core\Factory\ContentRepositoryId;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Model\Site;
-use Neos\Neos\Domain\Repository\DomainRepository;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Utility\NodeTypeWithFallbackProvider;
@@ -32,70 +30,69 @@ final class SiteNodeUtility
{
use NodeTypeWithFallbackProvider;
- #[Flow\Inject]
- protected ContentRepositoryRegistry $contentRepositoryRegistry;
-
public function __construct(
- private readonly DomainRepository $domainRepository,
- private readonly SiteRepository $siteRepository
+ private readonly ContentRepositoryRegistry $contentRepositoryRegistry
) {
}
- public function findSiteNode(Node $node): Node
- {
- $previousNode = null;
- $subgraph = $this->contentRepositoryRegistry->subgraphForNode($node);
- do {
- if ($this->getNodeType($node)->isOfType(NodeTypeNameFactory::NAME_SITES)) {
- // the Site node is the one level underneath the "Sites" node.
- if (is_null($previousNode)) {
- break;
- }
- return $previousNode;
- }
- $previousNode = $node;
- } while ($node = $subgraph->findParentNode($node->nodeAggregateId));
-
- // no Site node found at rootline
- throw new \RuntimeException('No site node found!');
- }
-
- public function findCurrentSiteNode(
- ContentRepositoryId $contentRepositoryId,
+ /**
+ * Find the site node by the neos site entity.
+ *
+ * To find the site node for the live workspace in a 0 dimensional content repository use:
+ *
+ * ```php
+ * $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId);
+ * $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive())
+ * ?? throw new \RuntimeException('Expected live workspace to exist.');
+ *
+ * $siteNode = $this->siteNodeUtility->findSiteNodeBySite(
+ * $site,
+ * $liveWorkspace->currentContentStreamId,
+ * DimensionSpacePoint::fromArray([]),
+ * VisibilityConstraints::frontend()
+ * );
+ * ```
+ *
+ * To resolve the Site by a node use {@see SiteRepository::findSiteBySiteNode()}
+ */
+ public function findSiteNodeBySite(
+ Site $site,
ContentStreamId $contentStreamId,
DimensionSpacePoint $dimensionSpacePoint,
VisibilityConstraints $visibilityConstraints
): Node {
- $domain = $this->domainRepository->findOneByActiveRequest();
- $site = $domain
- ? $domain->getSite()
- : $this->siteRepository->findDefault();
+ $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId);
- if ($site instanceof Site) {
- $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
- $subgraph = $contentRepository->getContentGraph()->getSubgraph(
- $contentStreamId,
- $dimensionSpacePoint,
- $visibilityConstraints,
- );
+ $subgraph = $contentRepository->getContentGraph()->getSubgraph(
+ $contentStreamId,
+ $dimensionSpacePoint,
+ $visibilityConstraints,
+ );
+
+ $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType(
+ $contentStreamId,
+ NodeTypeNameFactory::forSites()
+ );
+ $rootNode = $rootNodeAggregate->getNodeByCoveredDimensionSpacePoint($dimensionSpacePoint);
+
+ $siteNode = $subgraph->findChildNodeConnectedThroughEdgeName(
+ $rootNode->nodeAggregateId,
+ $site->getNodeName()->toNodeName()
+ );
+
+ if (!$siteNode) {
+ throw new \RuntimeException(sprintf('No site node found for site "%s"', $site->getNodeName()), 1697140379);
+ }
- $rootNodeAggregate = $contentRepository->getContentGraph()
- ->findRootNodeAggregateByType(
- $contentStreamId,
- NodeTypeNameFactory::forSites()
- );
- $sitesNode = $subgraph->findNodeById($rootNodeAggregate->nodeAggregateId);
- if ($sitesNode) {
- $siteNode = $subgraph->findChildNodeConnectedThroughEdgeName(
- $sitesNode->nodeAggregateId,
- $site->getNodeName()->toNodeName()
- );
- if ($siteNode instanceof Node) {
- return $siteNode;
- }
- }
+ if (!$this->getNodeType($siteNode)->isOfType(NodeTypeNameFactory::NAME_SITE)) {
+ throw new \RuntimeException(sprintf(
+ 'The site node "%s" (type: "%s") must be of type "%s"',
+ $siteNode->nodeAggregateId->value,
+ $siteNode->nodeTypeName->value,
+ NodeTypeNameFactory::NAME_SITE
+ ), 1697140367);
}
- throw new \RuntimeException('No site node found for domain "' . $domain?->getHostname() . '" and site "' . $site?->getNodeName() . '"');
+ return $siteNode;
}
}
diff --git a/Neos.Neos/Classes/Domain/Service/SiteService.php b/Neos.Neos/Classes/Domain/Service/SiteService.php
index 0331b8be881..c1fafe378bf 100644
--- a/Neos.Neos/Classes/Domain/Service/SiteService.php
+++ b/Neos.Neos/Classes/Domain/Service/SiteService.php
@@ -14,11 +14,10 @@
namespace Neos\Neos\Domain\Service;
+use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
-use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
-use Neos\ContentRepository\Core\Factory\ContentRepositoryId;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Media\Domain\Model\Asset;
@@ -65,12 +64,6 @@ class SiteService
*/
protected $assetCollectionRepository;
- #[Flow\Inject]
- protected SiteNodeUtility $siteNodeUtility;
-
- #[Flow\Inject]
- protected UserService $domainUserService;
-
/**
* Remove given site all nodes for that site and all domains associated.
*/
@@ -121,12 +114,12 @@ public function pruneAll()
*/
public function assignUploadedAssetToSiteAssetCollection(Asset $asset, Node $node, string $propertyName)
{
- try {
- $siteNode = $this->siteNodeUtility->findSiteNode($node);
- } catch (\InvalidArgumentException $exception) {
+ $subgraph = $this->contentRepositoryRegistry->subgraphForNode($node);
+ $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE));
+ if (!$siteNode) {
+ // should not happen
return;
}
-
if ($siteNode->nodeName === null) {
return;
}
diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
index 7c4ee09b84c..77c66be3809 100644
--- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
+++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
@@ -15,32 +15,22 @@
namespace Neos\Neos\Domain\Service;
use Neos\ContentRepository\Core\ContentRepository;
-use Neos\ContentRepository\Core\DimensionSpace\ContentDimensionZookeeper;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph;
+use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint;
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface;
-use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException;
-use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy;
-use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
+use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite;
use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate;
use Neos\ContentRepository\Core\Feature\NodeVariation\Command\CreateNodeVariant;
-use Neos\ContentRepository\Core\Feature\RootNodeCreation\Command\CreateRootNodeAggregateWithNode;
-use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
+use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
+use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate;
-use Neos\ContentRepository\Core\Projection\Workspace\Workspace;
use Neos\ContentRepository\Core\Service\ContentRepositoryBootstrapper;
+use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
-use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint;
-use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
-use Neos\ContentRepository\Core\NodeType\NodeTypeName;
-use Neos\ContentRepository\Core\SharedModel\User\UserId;
-use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
-use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription;
-use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
-use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle;
-use Neos\Neos\Domain\Exception\LiveWorkspaceIsMissing;
-use Neos\Neos\Domain\Exception\SitesNodeIsMissing;
+use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy;
+use Neos\Neos\Domain\Exception\SiteNodeTypeIsInvalid;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Model\SiteNodeName;
@@ -98,7 +88,7 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi
NodeTypeNameFactory::forSites()
);
try {
- $this->nodeTypeManager->getNodeType($nodeTypeName);
+ $siteNodeType = $this->nodeTypeManager->getNodeType($nodeTypeName);
} catch (NodeTypeNotFoundException $exception) {
throw new NodeTypeNotFoundException(
'Cannot create a site using a non-existing node type.',
@@ -107,6 +97,10 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi
);
}
+ if (!$siteNodeType->isOfType(NodeTypeNameFactory::NAME_SITE)) {
+ throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName));
+ }
+
$siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregatesByName(
$liveContentStreamId,
$sitesNodeIdentifier,
diff --git a/Neos.Neos/Classes/Fusion/Helper/SiteHelper.php b/Neos.Neos/Classes/Fusion/Helper/SiteHelper.php
index 05e8a283d5f..c00989affe8 100644
--- a/Neos.Neos/Classes/Fusion/Helper/SiteHelper.php
+++ b/Neos.Neos/Classes/Fusion/Helper/SiteHelper.php
@@ -14,16 +14,11 @@
namespace Neos\Neos\Fusion\Helper;
-use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
-use Neos\Neos\Domain\Model\Site;
-use Neos\Neos\Domain\Model\SiteNodeName;
-use Neos\Neos\Domain\Repository\SiteRepository;
-use Neos\Neos\FrontendRouting\NodeAddressFactory;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
-use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
-use Neos\Flow\Annotations as Flow;
use Neos\Eel\ProtectedContextAwareInterface;
-use Neos\Neos\Domain\Exception;
+use Neos\Flow\Annotations as Flow;
+use Neos\Neos\Domain\Model\Site;
+use Neos\Neos\Domain\Repository\SiteRepository;
/**
* Eel helper for accessing the Site object
@@ -36,17 +31,13 @@ class SiteHelper implements ProtectedContextAwareInterface
*/
protected $siteRepository;
- /**
- *
- * @throws Exception
- */
public function findBySiteNode(Node $siteNode): ?Site
{
- if ($siteNode->nodeName === null) {
+ try {
+ return $this->siteRepository->findSiteBySiteNode($siteNode);
+ } catch (\Neos\Neos\Domain\Exception) {
return null;
}
- $siteNodeName = SiteNodeName::fromNodeName($siteNode->nodeName);
- return $this->siteRepository->findOneByNodeName($siteNodeName);
}
/**
diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php
index 6910e8ffe6b..4406031ca8c 100644
--- a/Neos.Neos/Classes/View/FusionExceptionView.php
+++ b/Neos.Neos/Classes/View/FusionExceptionView.php
@@ -14,10 +14,8 @@
namespace Neos\Neos\View;
-use GuzzleHttp\Psr7\ServerRequest;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
-use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
@@ -36,10 +34,10 @@
use Neos\Fusion\Core\RuntimeFactory;
use Neos\Fusion\Exception\RuntimeException;
use Neos\Neos\Domain\Model\RenderingMode;
+use Neos\Neos\Domain\Repository\DomainRepository;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\FusionService;
use Neos\Neos\Domain\Service\SiteNodeUtility;
-use Neos\Neos\Domain\Service\RenderingModeService;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
class FusionExceptionView extends AbstractView
@@ -90,7 +88,7 @@ class FusionExceptionView extends AbstractView
protected ContentRepositoryRegistry $contentRepositoryRegistry;
#[Flow\Inject]
- protected RenderingModeService $userInterfaceModeService;
+ protected DomainRepository $domainRepository;
/**
* @return string
@@ -117,14 +115,14 @@ public function render()
);
$dimensionSpacePoint = $fusionExceptionViewInternals->getArbitraryDimensionSpacePoint();
- $contentStreamId = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive())
- ?->currentContentStreamId;
+ $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive());
$currentSiteNode = null;
- if ($contentStreamId instanceof ContentStreamId) {
- $currentSiteNode = $this->siteNodeUtility->findCurrentSiteNode(
- $siteDetectionResult->contentRepositoryId,
- $contentStreamId,
+ $site = $this->siteRepository->findOneByNodeName($siteDetectionResult->siteNodeName);
+ if ($liveWorkspace && $site) {
+ $currentSiteNode = $this->siteNodeUtility->findSiteNodeBySite(
+ $site,
+ $liveWorkspace->currentContentStreamId,
$dimensionSpacePoint,
VisibilityConstraints::frontend()
);
@@ -163,13 +161,12 @@ public function render()
try {
$output = $fusionRuntime->render('error');
- $output = $this->extractBodyFromOutput($output);
+ return $this->extractBodyFromOutput($output);
} catch (RuntimeException $exception) {
throw $exception->getPrevious() ?: $exception;
+ } finally {
+ $fusionRuntime->popContext();
}
- $fusionRuntime->popContext();
-
- return $output;
}
return '';
diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php
index 6fd019f4ad3..c507576e069 100644
--- a/Neos.Neos/Classes/View/FusionView.php
+++ b/Neos.Neos/Classes/View/FusionView.php
@@ -29,7 +29,6 @@
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\FusionService;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;
-use Neos\Neos\Domain\Service\SiteNodeUtility;
use Neos\Neos\Domain\Service\RenderingModeService;
use Neos\Neos\Exception;
use Neos\Neos\Utility\NodeTypeWithFallbackProvider;
@@ -46,12 +45,6 @@ class FusionView extends AbstractView
#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;
- /**
- * @Flow\Inject
- * @var SiteNodeUtility
- */
- protected $siteNodeUtility;
-
#[Flow\Inject]
protected RuntimeFactory $runtimeFactory;
@@ -72,7 +65,13 @@ public function render(): string|ResponseInterface
{
$currentNode = $this->getCurrentNode();
- $currentSiteNode = $this->siteNodeUtility->findSiteNode($currentNode);
+ $subgraph = $this->contentRepositoryRegistry->subgraphForNode($currentNode);
+ $currentSiteNode = $subgraph->findClosestNode($currentNode->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE));
+
+ if (!$currentSiteNode) {
+ throw new \RuntimeException('No site node found!', 1697053346);
+ }
+
$fusionRuntime = $this->getFusionRuntime($currentSiteNode);
$fusionRuntime->pushContextArray([
diff --git a/Neos.Neos/NodeTypes/Mixin/Document.yaml b/Neos.Neos/NodeTypes/Mixin/Document.yaml
index 41d37e48cea..f00b1f0dd85 100644
--- a/Neos.Neos/NodeTypes/Mixin/Document.yaml
+++ b/Neos.Neos/NodeTypes/Mixin/Document.yaml
@@ -11,6 +11,8 @@
constraints:
nodeTypes:
'*': false
+ # explicitly disallow to create a homepage below a regular document
+ 'Neos.Neos:Site': false
'Neos.Neos:Document': true
postprocessors:
'CreationDialogPostprocessor':
diff --git a/Neos.Neos/NodeTypes/Mixin/Site.yaml b/Neos.Neos/NodeTypes/Mixin/Site.yaml
new file mode 100644
index 00000000000..c2d38ef77a3
--- /dev/null
+++ b/Neos.Neos/NodeTypes/Mixin/Site.yaml
@@ -0,0 +1,10 @@
+#
+# Abstract NodeType for a Neos.Neos site.
+# Each homepage must extend this NodeType.
+# Nodes of this type must be direct children of the Neos.Neos:Site Root
+# and must not be created at any other place in the tree.
+#
+'Neos.Neos:Site':
+ abstract: true
+ superTypes:
+ 'Neos.Neos:Document': true
diff --git a/Neos.Neos/NodeTypes/Root/Sites.yaml b/Neos.Neos/NodeTypes/Root/Sites.yaml
index fa2556e5904..229957b03dd 100644
--- a/Neos.Neos/NodeTypes/Root/Sites.yaml
+++ b/Neos.Neos/NodeTypes/Root/Sites.yaml
@@ -1,3 +1,11 @@
+#
+# Root NodeType for a Neos.Neos content repository.
+# Any Neos Site node must be its direct child.
+#
'Neos.Neos:Sites':
superTypes:
'Neos.ContentRepository:Root': true
+ constraints:
+ nodeTypes:
+ '*': false
+ 'Neos.Neos:Site': true