diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index 36459b4aa3..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'] ?? $event->nodeAggregateId->value; + $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 index 2180393587..2135fe68e9 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MissingPathSegments.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MissingPathSegments.feature @@ -102,3 +102,47 @@ Feature: Routing functionality if path segments are missing like during tethered | 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 | {} | + | propertiesToUnset | ["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" + + 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"