Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

!!! FEATURE: Neos 9.0 compatibility #50

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<?php

namespace Flowpack\Listable\Fusion\Eel\FlowQueryOperations;

/* *
* This script belongs to the Flow package "Flowpack.Listable". *
* */

use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\Eel\FlowQuery\FlowQueryException;
use Neos\Eel\FlowQuery\Operations\AbstractOperation;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\Annotations as Flow;
use Neos\ContentRepository\Domain\Model\NodeInterface;

/**
* FlowQuery operation to filter Nodes by a date property
Expand All @@ -33,7 +34,7 @@ class FilterByDateOperation extends AbstractOperation
*/
public function canEvaluate($context)
{
return (!isset($context[0]) || ($context[0] instanceof NodeInterface));
return (!isset($context[0]) || ($context[0] instanceof Node));
}

/**
Expand Down Expand Up @@ -65,7 +66,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)

$filteredNodes = [];
foreach ($flowQuery->getContext() as $node) {
/** @var NodeInterface $node */
/** @var Node $node */
$propertyValue = $node->getProperty($filterByPropertyPath);
if (($compareOperator === '>' && $propertyValue > $date) || ($compareOperator === '<' && $propertyValue < $date)) {
$filteredNodes[] = $node;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<?php

namespace Flowpack\Listable\Fusion\Eel\FlowQueryOperations;

/* *
* This script belongs to the Flow package "Flowpack.Listable". *
* */

use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Eel\FlowQuery\FlowQueryException;
use Neos\Eel\FlowQuery\Operations\AbstractOperation;
use Neos\Flow\Annotations as Flow;
use Neos\ContentRepository\Domain\Model\NodeInterface;

/**
* FlowQuery operation to filter by properties of type reference or references
Expand All @@ -33,7 +34,7 @@ class FilterByReferenceOperation extends AbstractOperation
*/
public function canEvaluate($context)
{
return (!isset($context[0]) || ($context[0] instanceof NodeInterface));
return (!isset($context[0]) || ($context[0] instanceof Node));
}

/**
Expand All @@ -55,12 +56,12 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
throw new FlowQueryException('filterByReference() needs node reference by which nodes should be filtered', 1332493263);
}

/** @var NodeInterface $nodeReference */
/** @var Node $nodeReference */
list($filterByPropertyPath, $nodeReference) = $arguments;

$filteredNodes = [];
foreach ($flowQuery->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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
<?php

namespace Flowpack\Listable\Fusion\Eel\FlowQueryOperations;

/* *
* This script belongs to the Flow package "Flowpack.Listable". *
* */

use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindAncestorNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Eel\FlowQuery\Operations\AbstractOperation;

/**
* Sort Nodes by their position in the node tree.
* 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.
*
* Use it like this:
*
Expand All @@ -29,14 +36,17 @@ class SortRecursiveByIndexOperation extends AbstractOperation
*/
protected static $priority = 100;

#[\Neos\Flow\Annotations\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;

/**
* {@inheritdoc}
*
* We can only handle CR Nodes.
*/
public function canEvaluate($context)
{
return (isset($context[0]) && ($context[0] instanceof NodeInterface));
return (isset($context[0]) && ($context[0] instanceof Node)) || (is_array($context) && count($context) === 0);
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -50,42 +60,78 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
if (!empty($arguments[0]) && in_array($arguments[0], ['ASC', 'DESC'], true)) {
$sortOrder = $arguments[0];
}

$nodes = $flowQuery->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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely not your fault, but I find it really hard to review this. And since there are no tests, it's not easy to verify the desired behavior

Copy link
Contributor Author

@dlubitz dlubitz Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutly. But I can't provide that for each package, where I just want to help out to get this package compatible with Neos 9.

}

return 0;
});

$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.");
}
}
4 changes: 2 additions & 2 deletions Configuration/Routes.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
-
name: 'Paginate for Flowpack.Listable'
uriPattern: '{node}<pageSeparator>{currentPage}<defaultUriSuffix>'
uriPattern: '{node}<pageSeparator>{currentPage}'
defaults:
'@package': 'Neos.Neos'
'@controller': 'Frontend\Node'
'@format': 'html'
'@action': 'show'
routeParts:
node:
handler: Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface
handler: Neos\Neos\FrontendRouting\FrontendNodeRoutePartHandlerInterface
appendExceedingArguments: TRUE
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(['ASC'|'DESC']).get()}
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions Resources/Private/Fusion/Collection.fusion
Original file line number Diff line number Diff line change
@@ -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 = ${'<ul class="' + this.listClass + '">' + value + '</ul>'}

collection = 'must-be-set'
items = 'must-be-set'
itemName = 'node'
iterationName = 'iteration'
itemRenderer = Flowpack.Listable:ContentCaseShort
Expand Down
2 changes: 1 addition & 1 deletion Resources/Private/Fusion/List.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
8 changes: 4 additions & 4 deletions Resources/Private/Fusion/PaginatedCollection.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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}
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions Resources/Private/Fusion/Pagination.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 = ${'<ul class="' + props.class + '">' + value + '</ul>'}
collection = Flowpack.Listable:PaginationArray {
items = Flowpack.Listable:PaginationArray {
currentPage = ${props.currentPage}
maximumNumberOfLinks = ${props.maximumNumberOfLinks}
totalCount = ${props.totalCount}
Expand Down
2 changes: 1 addition & 1 deletion Resources/Private/Fusion/Utility.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down