diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index 4adcba2389..274a6c99cd 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -31,6 +31,7 @@ use Neos\ContentRepository\Core\Projection\ProjectionStatus; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; use Neos\EventStore\Model\EventEnvelope; use Neos\Neos\Domain\Model\SiteNodeName; use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException; @@ -209,7 +210,7 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre } $propertyValues = $event->initialPropertyValues->getPlainValues(); - $uriPathSegment = $propertyValues['uriPathSegment'] ?? ''; + $uriPathSegment = ($propertyValues['uriPathSegment'] ?? '') ?: $event->nodeAggregateId->value; $shortcutTarget = null; if ($documentTypeClassification === DocumentTypeClassification::CLASSIFICATION_SHORTCUT) { @@ -494,8 +495,10 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn return; } $newPropertyValues = $event->propertyValues->getPlainValues(); + $unsetPropertyNames = array_map(fn(PropertyName $propertyName) => $propertyName->value, iterator_to_array($event->propertiesToUnset->getIterator())); if ( !isset($newPropertyValues['uriPathSegment']) + && !in_array('uriPathSegment', $unsetPropertyNames) && !isset($newPropertyValues['targetMode']) && !isset($newPropertyValues['target']) ) { @@ -529,12 +532,12 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn ); } - if (!isset($newPropertyValues['uriPathSegment'])) { + if (!isset($newPropertyValues['uriPathSegment']) && !in_array('uriPathSegment', $unsetPropertyNames)) { continue; } $oldUriPath = $node->getUriPath(); $uriPathSegments = explode('/', $oldUriPath); - $uriPathSegments[array_key_last($uriPathSegments)] = $newPropertyValues['uriPathSegment']; + $uriPathSegments[array_key_last($uriPathSegments)] = ($newPropertyValues['uriPathSegment'] ?? '') ?: $event->nodeAggregateId; $newUriPath = implode('/', $uriPathSegments); $this->updateNodeQuery( diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MissingPathSegments.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MissingPathSegments.feature new file mode 100644 index 0000000000..00c4d1bc11 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MissingPathSegments.feature @@ -0,0 +1,148 @@ +@flowEntities @contentrepository +Feature: Routing functionality if path segments are missing like during tethered node creation + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': {} + 'Neos.Neos:Content': {} + 'Neos.Neos:Test.Routing.Page': + superTypes: + 'Neos.Neos:Document': true + properties: + uriPathSegment: + type: string + 'Neos.Neos:Test.Routing.Content': + superTypes: + 'Neos.Neos:Content': true + properties: + uriPathSegment: + type: string + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And I am in workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.Neos:Sites" | + + # lady-eleonode-rootford + # shernode-homes + # sir-david-nodenborough + # duke-of-contentshire (content node) + # earl-o-documentbourgh + # nody-mc-nodeface + # + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | shernode-homes | lady-eleonode-rootford | Neos.Neos:Test.Routing.Page | {} | node1 | + | sir-david-nodenborough | shernode-homes | Neos.Neos:Test.Routing.Page | {} | node2 | + | duke-of-contentshire | sir-david-nodenborough | Neos.Neos:Test.Routing.Content | {} | node3 | + | earl-o-documentbourgh | sir-david-nodenborough | Neos.Neos:Test.Routing.Page | {} | node4 | + | nody-mc-nodeface | shernode-homes | Neos.Neos:Test.Routing.Page | {} | node5 | + And A site exists for node name "node1" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + 'node1': + preset: 'default' + uriPathSuffix: '' + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + Scenario: Match homepage URL + When I am on URL "/" + Then the matched node should be "shernode-homes" in content stream "cs-identifier" and dimension "{}" + + Scenario: Resolve nodes correctly from homepage + When I am on URL "/" + Then the node "shernode-homes" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/" + And the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-o-documentbourgh" + + Scenario: Match node lower in the tree + When I am on URL "/sir-david-nodenborough/earl-o-documentbourgh" + Then the matched node should be "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" + + Scenario: Resolve from node lower in the tree + When I am on URL "/sir-david-nodenborough/earl-o-documentbourgh" + Then the node "shernode-homes" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/" + And the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-o-documentbourgh" + + Scenario: Add uri path segment on first level + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": "david-nodenborough-updated"} | + And I am on URL "/" + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated/earl-o-documentbourgh" + + Scenario: Add uri path segment on second level + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "earl-o-documentbourgh" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": "earl-documentbourgh-updated"} | + And I am on URL "/" + Then the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-documentbourgh-updated" + + Scenario: Add empty uri path segment on first level + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": ""} | + And I am on URL "/" + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-o-documentbourgh" + + Scenario: Uri path segment is unset after having been set before + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": "david-nodenborough-updated"} | + And I am on URL "/" + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated/earl-o-documentbourgh" + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": null} | + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-o-documentbourgh" + + Scenario: Uri path segment is set to empty string having been set before + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": "david-nodenborough-updated"} | + And I am on URL "/" + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough-updated/earl-o-documentbourgh" + When the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"uriPathSegment": ""} | + Then the node "sir-david-nodenborough" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough" + And the node "earl-o-documentbourgh" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/sir-david-nodenborough/earl-o-documentbourgh" diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature index 747782161d..8512383265 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature @@ -36,6 +36,7 @@ Feature: Test cases for node creation edge cases | nodeTypeName | "Neos.Neos:Site" | | parentNodeAggregateId | "lady-eleonode-rootford" | | originDimensionSpacePoint | {"example":"source"} | + | nodeName | "site" | And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | # Let's prepare some siblings to check orderings. Also, everything gets better with siblings. diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature index 7e4d76f0ca..b049cef11b 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature @@ -36,6 +36,7 @@ Feature: Test cases for node variation edge cases | nodeTypeName | "Neos.Neos:Site" | | parentNodeAggregateId | "lady-eleonode-rootford" | | originDimensionSpacePoint | {"example":"source"} | + | nodeName | "site" | And the command CreateNodeVariant is executed with payload: | Key | Value | | nodeAggregateId | "shernode-homes" | @@ -135,6 +136,7 @@ Feature: Test cases for node variation edge cases | nodeTypeName | "Neos.Neos:Site" | | parentNodeAggregateId | "lady-eleonode-rootford" | | originDimensionSpacePoint | {"example":"rootGeneral"} | + | nodeName | "site" | And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | # Let's create some siblings, both in source and target, to check ordering @@ -225,6 +227,7 @@ Feature: Test cases for node variation edge cases | nodeTypeName | "Neos.Neos:Site" | | parentNodeAggregateId | "lady-eleonode-rootford" | | originDimensionSpacePoint | {"example":"source"} | + | nodeName | "site" | And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | # Let's create our test subject... diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/PartialPublish.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/PartialPublish.feature index 803b0e8b5e..322a1346e6 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/PartialPublish.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/PartialPublish.feature @@ -36,6 +36,7 @@ Feature: Test cases for partial publish to live and uri path generation | nodeTypeName | "Neos.Neos:Site" | | parentNodeAggregateId | "lady-eleonode-rootford" | | originDimensionSpacePoint | {"example":"source"} | + | nodeName | "site" | And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "myworkspace" | @@ -49,7 +50,7 @@ Feature: Test cases for partial publish to live and uri path generation | nodeTypeName | "Neos.Neos:Document" | | parentNodeAggregateId | "shernode-homes" | | originDimensionSpacePoint | {"example":"source"} | - | properties | {"uriPathSegment": "just"}| + | initialPropertyValues | {"uriPathSegment": "just"}| | workspaceName | "myworkspace" | And the command PublishIndividualNodesFromWorkspace is executed with payload: | Key | Value | @@ -65,4 +66,4 @@ Feature: Test cases for partial publish to live and uri path generation | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | | "fbe53ddc3305685fbb4dbf529f283a0e" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | - | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes/justsomepage" | "justsomepage" | "shernode-homes" | null | null | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "just" | "lady-eleonode-rootford/shernode-homes/justsomepage" | "justsomepage" | "shernode-homes" | null | null | "Neos.Neos:Document" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature index 904990f72d..040b41d288 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature @@ -39,6 +39,7 @@ Feature: Basic routing functionality (match & resolve nodes with unknown types) | nodeTypeName | "Neos.Neos:Test.Routing.Page" | | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeAggregateClassification | "regular" | + | nodeName | "site" | And the event NodeAggregateWithNodeWasCreated was published with payload: | Key | Value | | workspaceName | "live" |