diff --git a/Classes/Fusion/Eel/FlowQueryOperations/FilterByDateOperation.php b/Classes/Fusion/Eel/FlowQueryOperations/FilterByDateOperation.php index 770133e..c3aeb00 100644 --- a/Classes/Fusion/Eel/FlowQueryOperations/FilterByDateOperation.php +++ b/Classes/Fusion/Eel/FlowQueryOperations/FilterByDateOperation.php @@ -1,15 +1,16 @@ getContext() as $node) { - /** @var NodeInterface $node */ + /** @var Node $node */ $propertyValue = $node->getProperty($filterByPropertyPath); if (($compareOperator === '>' && $propertyValue > $date) || ($compareOperator === '<' && $propertyValue < $date)) { $filteredNodes[] = $node; diff --git a/Classes/Fusion/Eel/FlowQueryOperations/FilterByReferenceOperation.php b/Classes/Fusion/Eel/FlowQueryOperations/FilterByReferenceOperation.php index f0265ef..1719a71 100644 --- a/Classes/Fusion/Eel/FlowQueryOperations/FilterByReferenceOperation.php +++ b/Classes/Fusion/Eel/FlowQueryOperations/FilterByReferenceOperation.php @@ -1,15 +1,16 @@ getContext() as $node) { - /** @var NodeInterface $node */ + /** @var Node $node */ $propertyValue = $node->getProperty($filterByPropertyPath); if ($nodeReference == $propertyValue || (is_array($propertyValue) && in_array($nodeReference, $propertyValue, false))) { $filteredNodes[] = $node; diff --git a/Classes/Fusion/Eel/FlowQueryOperations/SortRecursiveByIndexOperation.php b/Classes/Fusion/Eel/FlowQueryOperations/SortRecursiveByIndexOperation.php index 7e66981..c240954 100644 --- a/Classes/Fusion/Eel/FlowQueryOperations/SortRecursiveByIndexOperation.php +++ b/Classes/Fusion/Eel/FlowQueryOperations/SortRecursiveByIndexOperation.php @@ -1,17 +1,24 @@ getContext(); + if (count($nodes) <= 1) { + return; + } - $indexPathCache = []; + $pathMap = []; + $subgraph = $this->contentRepositoryRegistry->subgraphForNode($nodes[0]); - /** @var NodeInterface $node */ + // Collect all NodeAggregateId paths + /** @var Node $node */ foreach ($nodes as $node) { - // Collect the list of sorting indices for all parents of the node and the node itself - $nodeIdentifier = $node->getIdentifier(); - $indexPath = [$node->getIndex()]; - while ($node = $node->getParent()) { - $indexPath[] = $node->getIndex(); - } - $indexPathCache[$nodeIdentifier] = $indexPath; + $nodeIdentifier = $node->aggregateId->value; + $ancestors = $subgraph->findAncestorNodes($node->aggregateId, FindAncestorNodesFilter::create()); + $pathMap[$nodeIdentifier] = array_merge([$node->aggregateId], array_map(static fn($ancestor) => $ancestor->aggregateId, iterator_to_array($ancestors))); } $flip = $sortOrder === 'DESC' ? -1 : 1; - usort($nodes, function (NodeInterface $a, NodeInterface $b) use ($indexPathCache, $flip) { - if ($a === $b) { + usort($nodes, function (Node $a, Node $b) use ($subgraph, $pathMap, $flip) { + // Both nodes are equal + if ($a->equals($b)) { return 0; } - // Compare index path starting from the site root until a difference is found - $aIndexPath = $indexPathCache[$a->getIdentifier()]; - $bIndexPath = $indexPathCache[$b->getIdentifier()]; - while (count($aIndexPath) > 0 && count($bIndexPath) > 0) { - $diff = (array_pop($aIndexPath) - array_pop($bIndexPath)); - if ($diff !== 0) { - return $flip * $diff < 0 ? -1 : 1; + $commonParentPathSegmentNodeAggregateId = null; + $childNodesCache = []; + + // Compare path starting from the site root until a difference is found. + $aPath = $pathMap[$a->aggregateId->value]; + $bPath = $pathMap[$b->aggregateId->value]; + while (count($aPath) > 0 && count($bPath) > 0) { + + /** @var NodeAggregateId $aPathSegmentNodeAggregateId */ + $aPathSegmentNodeAggregateId = array_pop($aPath); + /** @var NodeAggregateId $bPathSegmentNodeAggregateId */ + $bPathSegmentNodeAggregateId = array_pop($bPath); + + $pathDiff = (!$aPathSegmentNodeAggregateId->equals($bPathSegmentNodeAggregateId)); + + if ($pathDiff === true) { + // Path is different at this segment, so we need to figure out their position under the last common parent. + if ($commonParentPathSegmentNodeAggregateId === null) { + return 0; + } + + if (!isset($childNodesCache[$commonParentPathSegmentNodeAggregateId->value])) { + $childNodesCache[$commonParentPathSegmentNodeAggregateId->value] = $subgraph->findChildNodes($commonParentPathSegmentNodeAggregateId, FindChildNodesFilter::create()); + } + + $positionDiff = $this->getIndexOfNodeAggregateIdInNodes($childNodesCache[$commonParentPathSegmentNodeAggregateId->value], $aPathSegmentNodeAggregateId) + - $this->getIndexOfNodeAggregateIdInNodes($childNodesCache[$commonParentPathSegmentNodeAggregateId->value], $bPathSegmentNodeAggregateId); + return $flip * $positionDiff < 0 ? -1 : 1; } + // No diff in path, we need to go deeper, or they are eventually equal + $commonParentPathSegmentNodeAggregateId = $aPathSegmentNodeAggregateId; } return 0; @@ -88,4 +123,15 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) $flowQuery->setContext($nodes); } + + private function getIndexOfNodeAggregateIdInNodes(Nodes $childNodes, NodeAggregateId $nodeAggregateId): int + { + foreach ($childNodes as $key => $childNode) { + if ($childNode->aggregateId->value === $nodeAggregateId->value) { + return $key; + } + } + + throw new \Exception("Exception on sorting nodes by there position in content tree."); + } } diff --git a/Configuration/Routes.yaml b/Configuration/Routes.yaml index de915c8..e022273 100644 --- a/Configuration/Routes.yaml +++ b/Configuration/Routes.yaml @@ -1,6 +1,6 @@ - name: 'Paginate for Flowpack.Listable' - uriPattern: '{node}{currentPage}' + uriPattern: '{node}{currentPage}' defaults: '@package': 'Neos.Neos' '@controller': 'Frontend\Node' @@ -8,5 +8,5 @@ '@action': 'show' routeParts: node: - handler: Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface + handler: Neos\Neos\FrontendRouting\FrontendNodeRoutePartHandlerInterface appendExceedingArguments: TRUE diff --git a/README.md b/README.md index b2ecd99..1820e40 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,8 @@ Filter nodes by properties of type reference or references. ## sortRecursiveByIndex -Sort nodes recursively by their sorting property. +Sort nodes by their position in the node tree. Please use with care, as this can become a very expensive operation, if you operate on bigger subtrees. Example: - ${q(site).find('[instanceof Neos.Neos:Document]').sortRecursiveByIndex('DESC').get()} + ${q(node).children("main").sortRecursiveByIndex('DESC').get()} diff --git a/Resources/Private/Fusion/Collection.fusion b/Resources/Private/Fusion/Collection.fusion index 7925b81..c6d913f 100644 --- a/Resources/Private/Fusion/Collection.fusion +++ b/Resources/Private/Fusion/Collection.fusion @@ -1,10 +1,10 @@ -prototype(Flowpack.Listable:Collection) < prototype(Neos.Fusion:Collection) { +prototype(Flowpack.Listable:Collection) < prototype(Neos.Fusion:Loop) { listClass = '' itemClass = '' @context.itemClass = ${this.itemClass} @process.tmpl = ${'
    ' + value + '
'} - collection = 'must-be-set' + items = 'must-be-set' itemName = 'node' iterationName = 'iteration' itemRenderer = Flowpack.Listable:ContentCaseShort diff --git a/Resources/Private/Fusion/List.fusion b/Resources/Private/Fusion/List.fusion index 03a756b..474a224 100644 --- a/Resources/Private/Fusion/List.fusion +++ b/Resources/Private/Fusion/List.fusion @@ -15,7 +15,7 @@ prototype(Flowpack.Listable:List) < prototype(Neos.Fusion:Component) { @if.listNotEmpty = ${props.list != null} attributes.class = ${props.wrapClass} - content = Neos.Fusion:Array { + content = Neos.Fusion:Join { listTitleTag = Neos.Fusion:Tag { tagName = 'h2' attributes.class = ${props.listTitleClass} diff --git a/Resources/Private/Fusion/PaginatedCollection.fusion b/Resources/Private/Fusion/PaginatedCollection.fusion index fbfb039..8505fb3 100644 --- a/Resources/Private/Fusion/PaginatedCollection.fusion +++ b/Resources/Private/Fusion/PaginatedCollection.fusion @@ -9,8 +9,8 @@ prototype(Flowpack.Listable:PaginatedCollection) < prototype(Neos.Fusion:Compone showPreviousNextLinks = false listRenderer = 'Flowpack.Listable:Collection' - renderer = Neos.Fusion:Array { - @context.data = Neos.Fusion:RawArray { + renderer = Neos.Fusion:Join { + @context.data = Neos.Fusion:DataStructure { collection = Neos.Fusion:Case { @context.limit = ${props.currentPage * props.itemsPerPage} @context.offset = ${(props.currentPage - 1) * props.itemsPerPage} @@ -33,7 +33,7 @@ prototype(Flowpack.Listable:PaginatedCollection) < prototype(Neos.Fusion:Compone list = Neos.Fusion:Renderer { type = ${props.listRenderer} - element.collection = ${data.collection} + element.items = ${data.collection} } pagination = Flowpack.Listable:Pagination { currentPage = ${props.currentPage} @@ -47,7 +47,7 @@ prototype(Flowpack.Listable:PaginatedCollection) < prototype(Neos.Fusion:Compone @cache { mode = 'dynamic' entryIdentifier { - node = ${node} + node = ${Neos.Caching.entryIdentifierForNode(node)} } entryDiscriminator = ${request.arguments.currentPage} context { diff --git a/Resources/Private/Fusion/Pagination.fusion b/Resources/Private/Fusion/Pagination.fusion index 5a8f41e..e4350d2 100644 --- a/Resources/Private/Fusion/Pagination.fusion +++ b/Resources/Private/Fusion/Pagination.fusion @@ -7,7 +7,7 @@ prototype(Flowpack.Listable:PaginationArray) { showPreviousNextLinks = false } -prototype(Flowpack.Listable:PaginationParameters) < prototype(Neos.Fusion:RawArray) +prototype(Flowpack.Listable:PaginationParameters) < prototype(Neos.Fusion:DataStructure) prototype(Flowpack.Listable:Pagination) < prototype(Neos.Fusion:Component) { totalCount = 'to-be-set' @@ -20,10 +20,10 @@ prototype(Flowpack.Listable:Pagination) < prototype(Neos.Fusion:Component) { currentItemClass = 'isCurrent' currentPage = ${request.arguments.currentPage || 1} - renderer = Neos.Fusion:Collection { + renderer = Neos.Fusion:Loop { @if.paginationNeeded = ${(props.totalCount/props.itemsPerPage) > 1} @process.tmpl = ${'
    ' + value + '
'} - collection = Flowpack.Listable:PaginationArray { + items = Flowpack.Listable:PaginationArray { currentPage = ${props.currentPage} maximumNumberOfLinks = ${props.maximumNumberOfLinks} totalCount = ${props.totalCount} diff --git a/Resources/Private/Fusion/Utility.fusion b/Resources/Private/Fusion/Utility.fusion index 0d56ef9..af24877 100644 --- a/Resources/Private/Fusion/Utility.fusion +++ b/Resources/Private/Fusion/Utility.fusion @@ -3,6 +3,6 @@ prototype(Flowpack.Listable:ContentCaseShort) < prototype(Neos.Neos:ContentCase) default { @position = 'end' condition = true - type = ${q(node).property('_nodeType.name') + '.Short'} + type = ${node.nodeTypeName + '.Short'} } } diff --git a/composer.json b/composer.json index 4ed6277..e460b52 100755 --- a/composer.json +++ b/composer.json @@ -7,7 +7,8 @@ "description": "Tiny extension for listing things", "license": "MIT", "require": { - "neos/neos": "^3.3 || ^4.0 || ^5.0 || ^7.0 || ^8.0 || dev-master" + "php": ">=8.2", + "neos/neos": "^9.0" }, "autoload": { "psr-4": {