From 17f04d7be4bd5d39e2bae6de52bd3744789660ca Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:22:59 +0200 Subject: [PATCH 01/17] TASK: Provide helper to find out if node is disabled in fusion As we don't support property('_hidden') there would be no way to figure out if a node is hidden. --- Neos.Neos/Classes/Fusion/Helper/NodeHelper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php index 5317ca9a715..e5c83617bb9 100644 --- a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php @@ -14,6 +14,7 @@ namespace Neos\Neos\Fusion\Helper; +use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\Projection\ContentGraph\AbsoluteNodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -162,6 +163,11 @@ public function subgraphForNode(Node $node): ContentSubgraphInterface return $this->contentRepositoryRegistry->subgraphForNode($node); } + public function isDisabled(Node $node): bool + { + return $node->tags->contain(SubtreeTag::disabled()); + } + /** * @param string $methodName * @return boolean From 9884e6c724b71dd070803a37a197971900669726 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:26:40 +0200 Subject: [PATCH 02/17] !!! TASK: Remove underscore node property access `_*` from property and filter FlowQuery Fixes https://github.com/neos/neos-development-collection/issues/4208#issuecomment-1573345069 --- .../Classes/FlowQueryOperations/FilterOperation.php | 7 ++----- .../Classes/FlowQueryOperations/PropertyOperation.php | 5 ----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php index 9b1107ddef4..8a5b9517845 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php @@ -16,7 +16,6 @@ use Neos\Eel\FlowQuery\FlowQuery; use Neos\Flow\Annotations as Flow; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; -use Neos\Utility\ObjectAccess; /** * This filter implementation contains specific behavior for use on ContentRepository @@ -122,11 +121,9 @@ protected function getPropertyPath($element, $propertyPath) if ($propertyPath === '_identifier') { // TODO: deprecated (Neos <9 case) return $element->aggregateId->value; - } elseif ($propertyPath[0] === '_') { - return ObjectAccess::getPropertyPath($element, substr($propertyPath, 1)); - } else { - return $element->getProperty($propertyPath); } + + return $element->getProperty($propertyPath); } /** diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index c6b72e2dbe0..ba7d67fb046 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -21,7 +21,6 @@ use Neos\Eel\FlowQuery\Operations\AbstractOperation; use Neos\Flow\Annotations as Flow; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; -use Neos\Utility\ObjectAccess; /** * Used to access properties of a ContentRepository Node. If the property mame is @@ -107,10 +106,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed return $element->aggregateId->value; } - if ($propertyName[0] === '_') { - return ObjectAccess::getPropertyPath($element, substr($propertyName, 1)); - } - $contentRepository = $this->contentRepositoryRegistry->get($element->contentRepositoryId); $nodeTypeManager = $contentRepository->getNodeTypeManager(); From 00d82d1bef29c1f0750ecb6966569a733bdc95dc Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:31:09 +0200 Subject: [PATCH 03/17] TASK: Remove legacy b/c for `property('_path')` This migration is handled via rector: https://github.com/neos/rector/blob/4843c45f5bd901bd63aad0d1d0367df394e2cd1e/config/set/contentrepository-90.php#L174C25-L174C45 --- .../Classes/FlowQueryOperations/PropertyOperation.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index ba7d67fb046..3dbd3b45220 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -92,15 +92,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed /* @var $element Node */ $element = $context[0]; - if ($propertyName === '_path') { - $subgraph = $this->contentRepositoryRegistry->subgraphForNode($element); - $ancestors = $subgraph->findAncestorNodes( - $element->aggregateId, - FindAncestorNodesFilter::create() - )->reverse(); - return AbsoluteNodePath::fromLeafNodeAndAncestors($element, $ancestors)->serializeToString(); - } if ($propertyName === '_identifier') { // TODO: deprecated (Neos <9 case) return $element->aggregateId->value; From 0746dba70d05eced003c29224d92b9e6a6969e9f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:31:41 +0200 Subject: [PATCH 04/17] TASK: Remove legacy b/c for `property('_identifier')` This migration is handled via rector: https://github.com/neos/rector/blob/4843c45f5bd901bd63aad0d1d0367df394e2cd1e/config/set/contentrepository-90.php#L191C25-L191C51 --- .../Classes/FlowQueryOperations/FilterOperation.php | 5 ----- .../Classes/FlowQueryOperations/PropertyOperation.php | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php index 8a5b9517845..57e08b5aefc 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php @@ -118,11 +118,6 @@ protected function matchesIdentifierFilter($element, $identifier) */ protected function getPropertyPath($element, $propertyPath) { - if ($propertyPath === '_identifier') { - // TODO: deprecated (Neos <9 case) - return $element->aggregateId->value; - } - return $element->getProperty($propertyPath); } diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index 3dbd3b45220..85227ed0e5e 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -93,11 +93,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed /* @var $element Node */ $element = $context[0]; - if ($propertyName === '_identifier') { - // TODO: deprecated (Neos <9 case) - return $element->aggregateId->value; - } - $contentRepository = $this->contentRepositoryRegistry->get($element->contentRepositoryId); $nodeTypeManager = $contentRepository->getNodeTypeManager(); From cd77d45f6437f541e2ca7aa6bb774dd54afce389 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:35:31 +0200 Subject: [PATCH 05/17] TASK: Move orphaned `SortOperation` to other Node eel flow-queries --- .../Classes}/FlowQueryOperations/SortOperation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {Neos.Neos/Classes/Eel => Neos.ContentRepository.NodeAccess/Classes}/FlowQueryOperations/SortOperation.php (98%) diff --git a/Neos.Neos/Classes/Eel/FlowQueryOperations/SortOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php similarity index 98% rename from Neos.Neos/Classes/Eel/FlowQueryOperations/SortOperation.php rename to Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php index a2958005f6e..6b00ac65500 100644 --- a/Neos.Neos/Classes/Eel/FlowQueryOperations/SortOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Eel\FlowQueryOperations; +namespace Neos\ContentRepository\NodeAccess\FlowQueryOperations; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Eel\FlowQuery\FlowQuery; From 8e4298f0dca676549e68a8044c0632af5434d69b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:38:09 +0200 Subject: [PATCH 06/17] !!! TASK: Remove underscore node property access `sort('_*')` from sort FlowQuery --- .../Classes/FlowQueryOperations/SortOperation.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php index 6b00ac65500..38464a0ab02 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php @@ -113,12 +113,9 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) // Determine the property value to sort by foreach ($nodes as $node) { - if ($sortProperty[0] === '_') { - $propertyValue = \Neos\Utility\ObjectAccess::getPropertyPath($node, substr($sortProperty, 1)); - } else { - $propertyValue = $node->getProperty($sortProperty); - } + $propertyValue = $node->getProperty($sortProperty); + // todo how to sort by creation date in Neos 9?? Something like node.timestamps.originalCreated if ($propertyValue instanceof \DateTime) { $propertyValue = $propertyValue->getTimestamp(); } From 5f8579c51efd5f29b2f421eb2cd9822df865a4fd Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 May 2024 13:41:50 +0200 Subject: [PATCH 07/17] TASK: Adjust docs of `PropertyOperation` --- .../Classes/FlowQueryOperations/PropertyOperation.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index 85227ed0e5e..409a5dd3c4a 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -23,9 +23,7 @@ use Neos\Neos\Utility\NodeTypeWithFallbackProvider; /** - * Used to access properties of a ContentRepository Node. If the property mame is - * prefixed with _, internal node properties like start time, end time, - * hidden are accessed. + * Used to access properties of a ContentRepository Node. */ class PropertyOperation extends AbstractOperation { @@ -80,7 +78,7 @@ public function canEvaluate($context): bool public function evaluate(FlowQuery $flowQuery, array $arguments): mixed { if (empty($arguments[0])) { - throw new FlowQueryException('property() does not support returning all attributes yet', 1332492263); + throw new FlowQueryException(static::$shortName . '() does not allow returning all properties.', 1332492263); } /** @var array $context */ $context = $flowQuery->getContext(); From 7554048c43dd8bf851839f735ad6139c4376bca6 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:56:42 +0200 Subject: [PATCH 08/17] TASK: Add behat test for sort operation --- .../Features/Fusion/FlowQuery.feature | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index 6e1c2effe20..5be1c0baea3 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -396,6 +396,29 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. nothingToRemove: a1a4,a1a4,a1a4 """ + Scenario: Sort + When I execute the following Fusion code: + """fusion + test = Neos.Fusion:DataStructure { + @context { + a1a1 = ${q(site).find('#a1a1').get(0)} + a1a2 = ${q(site).find('#a1a2').get(0)} + a1a3 = ${q(site).find('#a1a3').get(0)} + a1a4 = ${q(site).find('#a1a4').get(0)} + } + unsorted = ${q([a1a3, a1a4, a1a1, a1a2]).get()} + sortByTitleAsc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("title", "ASC").get()} + sortByUriDesc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("uriPathSegment", "DESC").get()} + @process.render = Neos.Neos:Test.RenderNodesDataStructure + } + """ + Then I expect the following Fusion rendering result: + """ + unsorted: a1a3,a1a4,a1a1,a1a2 + sortByTitleAsc: a1a1,a1a2,a1a3,a1a4 + sortByUriDesc: a1a4,a1a3,a1a2,a1a1 + """ + Scenario: Node accessors (final Node access operations) When the Fusion context node is "a1" When I execute the following Fusion code: From 1be047ad969049820e8c0e1d8a56d5541a5f8546 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:01:15 +0200 Subject: [PATCH 09/17] TASK: Readd unit test for sort operation was previously removed via https://github.com/neos/neos-development-collection/pull/5036 --- .../FlowQueryOperations/SortOperationTest.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php diff --git a/Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php b/Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php new file mode 100644 index 00000000000..8a7dc10b34f --- /dev/null +++ b/Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php @@ -0,0 +1,48 @@ +expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortOperation(); + $operation->evaluate($flowQuery, []); + } + + /** + * @test + */ + public function invalidSortDirectionCausesException() + { + $this->expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortOperation(); + $operation->evaluate($flowQuery, ['title', 'FOO']); + } + + /** + * @test + */ + public function invalidSortOptionCausesException() + { + $this->expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortOperation(); + $operation->evaluate($flowQuery, ['title', 'ASC', 'SORT_BAR']); + } +} From 9b2b607608a3d35bc9551ccd6752310e1cf322ae Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:54:39 +0200 Subject: [PATCH 10/17] TASK: Reimplement _creationDateTime, _lastModificationDateTime and _lastPublicationDateTime support for `sort` --- .../FlowQueryOperations/SortOperation.php | 16 +++++++--------- .../Behavior/Features/Fusion/FlowQuery.feature | 3 +++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php index 38464a0ab02..c6ab31737ae 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php @@ -46,9 +46,7 @@ public function canEvaluate($context) } /** - * {@inheritdoc} - * - * First argument is the node property to sort by. Works with internal arguments (_xyz) as well. + * First argument is the node property to sort by. To sort by time _creationDateTime, _lastModificationDateTime or _lastPublicationDateTime can be used. * Second argument is the sort direction (ASC or DESC). * Third optional argument are the sort options (see https://www.php.net/manual/en/function.sort): * - 'SORT_REGULAR' @@ -113,12 +111,12 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) // Determine the property value to sort by foreach ($nodes as $node) { - $propertyValue = $node->getProperty($sortProperty); - - // todo how to sort by creation date in Neos 9?? Something like node.timestamps.originalCreated - if ($propertyValue instanceof \DateTime) { - $propertyValue = $propertyValue->getTimestamp(); - } + $propertyValue = match($sortProperty) { + '_creationDateTime' => $node->timestamps->created->getTimestamp(), + '_lastModificationDateTime' => $node->timestamps->lastModified?->getTimestamp(), + '_lastPublicationDateTime' => $node->timestamps->originalLastModified?->getTimestamp(), + default => $node->getProperty($sortProperty) + }; $sortSequence[$node->aggregateId->value] = $propertyValue; $nodesByIdentifier[$node->aggregateId->value] = $node; diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index 5be1c0baea3..75c316d6684 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -409,6 +409,8 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. unsorted = ${q([a1a3, a1a4, a1a1, a1a2]).get()} sortByTitleAsc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("title", "ASC").get()} sortByUriDesc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("uriPathSegment", "DESC").get()} + # todo find out how to test time related logic + sortByDateAsc = ${q([a1a1]).sort("_creationDateTime", "ASC").get()} @process.render = Neos.Neos:Test.RenderNodesDataStructure } """ @@ -417,6 +419,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. unsorted: a1a3,a1a4,a1a1,a1a2 sortByTitleAsc: a1a1,a1a2,a1a3,a1a4 sortByUriDesc: a1a4,a1a3,a1a2,a1a1 + sortByDateAsc: a1a1 """ Scenario: Node accessors (final Node access operations) From 3ebe2a425e77fe4136e160c6917451c6b35291bf Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:06:33 +0200 Subject: [PATCH 11/17] TASK: Simplify happy path for q().property We should not need to load the node type for every fetch, only if we want to fetch references --- .../Classes/FlowQueryOperations/PropertyOperation.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index 409a5dd3c4a..9e4674d6935 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -90,6 +90,9 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed /* @var $element Node */ $element = $context[0]; + if ($element->hasProperty($propertyName)) { + return $element->getProperty($propertyName); + } $contentRepository = $this->contentRepositoryRegistry->get($element->contentRepositoryId); $nodeTypeManager = $contentRepository->getNodeTypeManager(); @@ -113,6 +116,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed return $references; } - return $element->getProperty($propertyName); + return null; } } From 8c72a52476760a9a657950f690230448c71a2fb5 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:36:17 +0200 Subject: [PATCH 12/17] TASK: Remove obsolete ParentsOperationTest --- .../ParentsOperationTest.php | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 Neos.Neos/Tests/Unit/FlowQueryOperations/ParentsOperationTest.php diff --git a/Neos.Neos/Tests/Unit/FlowQueryOperations/ParentsOperationTest.php b/Neos.Neos/Tests/Unit/FlowQueryOperations/ParentsOperationTest.php deleted file mode 100644 index 6717fbf0ccd..00000000000 --- a/Neos.Neos/Tests/Unit/FlowQueryOperations/ParentsOperationTest.php +++ /dev/null @@ -1,61 +0,0 @@ -markTestSkipped('TODO - Update with Neos 9.0'); - - $rootNode = $this->createMock(TraversableNode::class); - $sitesNode = $this->createMock(TraversableNode::class); - $siteNode = $this->createMock(TraversableNode::class); - $firstLevelNode = $this->createMock(TraversableNode::class); - $secondLevelNode = $this->createMock(TraversableNode::class); - - $rootNode->expects(self::any())->method('findNodePath')->will(self::returnValue(NodePath::fromString('/'))); - $rootNode->expects(self::any())->method('findParentNode')->will(self::throwException(new NodeException('No parent'))); - $sitesNode->expects(self::any())->method('findNodePath')->will(self::returnValue(NodePath::fromString('/sites'))); - $sitesNode->expects(self::any())->method('findParentNode')->will(self::returnValue($rootNode)); - $siteNode->expects(self::any())->method('findNodePath')->will(self::returnValue(NodePath::fromString('/sites/site'))); - $siteNode->expects(self::any())->method('findParentNode')->will(self::returnValue($sitesNode)); - $firstLevelNode->expects(self::any())->method('findParentNode')->will(self::returnValue($siteNode)); - $firstLevelNode->expects(self::any())->method('findNodePath')->will(self::returnValue(NodePath::fromString('/sites/site/first'))); - $secondLevelNode->expects(self::any())->method('findParentNode')->will(self::returnValue($firstLevelNode)); - $secondLevelNode->expects(self::any())->method('findNodePath')->will(self::returnValue(NodePath::fromString('/sites/site/first/second'))); - - $context = [$secondLevelNode]; - $q = new FlowQuery($context); - - $operation = new ParentsOperation(); - $operation->evaluate($q, []); - - $ancestors = $q->getContext(); - self::assertEquals([$siteNode, $firstLevelNode], $ancestors); - } -} From c1fb30123f0ccb5d9b5e0456c812f96c674d9fce Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:42:44 +0200 Subject: [PATCH 13/17] FEATURE: Introduce sortByTimestamp fq --- .../SortByTimeStampOperation.php | 99 +++++++++++++++++++ .../FlowQueryOperations/SortOperation.php | 9 +- .../SortByTimeStampOperationTest.php | 48 +++++++++ .../FlowQueryOperations/SortOperationTest.php | 2 +- .../Features/Fusion/FlowQuery.feature | 2 +- 5 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php create mode 100644 Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php rename {Neos.Neos => Neos.ContentRepository.NodeAccess}/Tests/Unit/FlowQueryOperations/SortOperationTest.php (94%) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php new file mode 100644 index 00000000000..e19060ca0f2 --- /dev/null +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php @@ -0,0 +1,99 @@ + $arguments the arguments for this operation. + * @return void + * @throws FlowQueryException + */ + public function evaluate(FlowQuery $flowQuery, array $arguments) + { + /** @var array|Node[] $nodes */ + $nodes = $flowQuery->getContext(); + + $sortedNodes = []; + $sortSequence = []; + $nodesByIdentifier = []; + + // Determine the property value to sort by + foreach ($nodes as $node) { + $timeStamp = match($arguments[0] ?? null) { + 'created' => $node->timestamps->created->getTimestamp(), + 'lastModified' => $node->timestamps->lastModified?->getTimestamp(), + 'originalLastModified' => $node->timestamps->originalLastModified?->getTimestamp(), + default => throw new FlowQueryException('Please provide a timestamp (created, lastModified, originalLastModified) to sort by.', 1727367726) + }; + + $sortSequence[$node->aggregateId->value] = $timeStamp; + $nodesByIdentifier[$node->aggregateId->value] = $node; + } + + $sortOrder = is_string($arguments[1] ?? null) ? strtoupper($arguments[1]) : null; + if ($sortOrder === 'DESC') { + arsort($sortSequence); + } elseif ($sortOrder === 'ASC') { + asort($sortSequence); + } else { + throw new FlowQueryException('Please provide a valid sort direction (ASC or DESC)', 1727367837); + } + + // Build the sorted context that is returned + foreach ($sortSequence as $nodeIdentifier => $value) { + $sortedNodes[] = $nodesByIdentifier[$nodeIdentifier]; + } + + $flowQuery->setContext($sortedNodes); + } +} diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php index c6ab31737ae..830e3f988c3 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortOperation.php @@ -46,7 +46,7 @@ public function canEvaluate($context) } /** - * First argument is the node property to sort by. To sort by time _creationDateTime, _lastModificationDateTime or _lastPublicationDateTime can be used. + * First argument is the node property to sort by. * Second argument is the sort direction (ASC or DESC). * Third optional argument are the sort options (see https://www.php.net/manual/en/function.sort): * - 'SORT_REGULAR' @@ -111,12 +111,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) // Determine the property value to sort by foreach ($nodes as $node) { - $propertyValue = match($sortProperty) { - '_creationDateTime' => $node->timestamps->created->getTimestamp(), - '_lastModificationDateTime' => $node->timestamps->lastModified?->getTimestamp(), - '_lastPublicationDateTime' => $node->timestamps->originalLastModified?->getTimestamp(), - default => $node->getProperty($sortProperty) - }; + $propertyValue = $node->getProperty($sortProperty); $sortSequence[$node->aggregateId->value] = $propertyValue; $nodesByIdentifier[$node->aggregateId->value] = $node; diff --git a/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php new file mode 100644 index 00000000000..d9171075e46 --- /dev/null +++ b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php @@ -0,0 +1,48 @@ +expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortByTimeStampOperation(); + $operation->evaluate($flowQuery, []); + } + + /** + * @test+ + */ + public function callWithoutWrongTimeStampArgumentsCausesException() + { + $this->expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortByTimeStampOperation(); + $operation->evaluate($flowQuery, ['erstellt']); + } + + /** + * @test + */ + public function invalidSortDirectionCausesException() + { + $this->expectException(FlowQueryException::class); + $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); + $operation = new SortByTimeStampOperation(); + $operation->evaluate($flowQuery, ['created', 'FOO']); + } +} diff --git a/Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortOperationTest.php similarity index 94% rename from Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php rename to Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortOperationTest.php index 8a7dc10b34f..a3bb5276452 100644 --- a/Neos.Neos/Tests/Unit/FlowQueryOperations/SortOperationTest.php +++ b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortOperationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Neos\Neos\Tests\Unit\FlowQueryOperations; +namespace Neos\ContentRepository\NodeAccess\Tests\Unit\FlowQueryOperations; use Neos\ContentRepository\NodeAccess\FlowQueryOperations\SortOperation; use Neos\Eel\FlowQuery\FlowQueryException; diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index 1065f84414e..d0da659a04e 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -409,7 +409,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. sortByTitleAsc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("title", "ASC").get()} sortByUriDesc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("uriPathSegment", "DESC").get()} # todo find out how to test time related logic - sortByDateAsc = ${q([a1a1]).sort("_creationDateTime", "ASC").get()} + sortByDateAsc = ${q([a1a1]).sortByTimestamp("created", "ASC").get()} @process.render = Neos.Neos:Test.RenderNodesDataStructure } """ From 00c9ee4f48287d6a1badaaefda05ad8b51603bed Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:49:08 +0200 Subject: [PATCH 14/17] TASK: Correct casing of `SortByTimeStampOperation` --- ...StampOperation.php => SortByTimestampOperation.php} | 2 +- ...rationTest.php => SortByTimestampOperationTest.php} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/{SortByTimeStampOperation.php => SortByTimestampOperation.php} (98%) rename Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/{SortByTimeStampOperationTest.php => SortByTimestampOperationTest.php} (82%) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php similarity index 98% rename from Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php rename to Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php index e19060ca0f2..5002078645f 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimeStampOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php @@ -23,7 +23,7 @@ * "sortByTimeStamp" operation working on ContentRepository nodes. * Sorts nodes by specified timestamp. */ -class SortByTimeStampOperation extends AbstractOperation +class SortByTimestampOperation extends AbstractOperation { /** * {@inheritdoc} diff --git a/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimestampOperationTest.php similarity index 82% rename from Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php rename to Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimestampOperationTest.php index d9171075e46..87914e0e733 100644 --- a/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimeStampOperationTest.php +++ b/Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/SortByTimestampOperationTest.php @@ -4,14 +4,14 @@ namespace Neos\ContentRepository\NodeAccess\Tests\Unit\FlowQueryOperations; -use Neos\ContentRepository\NodeAccess\FlowQueryOperations\SortByTimeStampOperation; +use Neos\ContentRepository\NodeAccess\FlowQueryOperations\SortByTimestampOperation; use Neos\Eel\FlowQuery\FlowQueryException; use PHPUnit\Framework\TestCase; /** * SortOperation test */ -class SortByTimeStampOperationTest extends TestCase +class SortByTimestampOperationTest extends TestCase { /** * @test+ @@ -20,7 +20,7 @@ public function callWithoutArgumentsCausesException() { $this->expectException(FlowQueryException::class); $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortByTimeStampOperation(); + $operation = new SortByTimestampOperation(); $operation->evaluate($flowQuery, []); } @@ -31,7 +31,7 @@ public function callWithoutWrongTimeStampArgumentsCausesException() { $this->expectException(FlowQueryException::class); $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortByTimeStampOperation(); + $operation = new SortByTimestampOperation(); $operation->evaluate($flowQuery, ['erstellt']); } @@ -42,7 +42,7 @@ public function invalidSortDirectionCausesException() { $this->expectException(FlowQueryException::class); $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortByTimeStampOperation(); + $operation = new SortByTimestampOperation(); $operation->evaluate($flowQuery, ['created', 'FOO']); } } From 546f39879b5b6db462f004fd50651f905f0f8d54 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 27 Sep 2024 16:54:04 +0200 Subject: [PATCH 15/17] TASK: Correct casing of SortByTimestampOperation --- .../Classes/FlowQueryOperations/SortByTimestampOperation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php index 5002078645f..bd08f3489d4 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php @@ -20,7 +20,7 @@ use Neos\Eel\FlowQuery\Operations\AbstractOperation; /** - * "sortByTimeStamp" operation working on ContentRepository nodes. + * "sortByTimestamp" operation working on ContentRepository nodes. * Sorts nodes by specified timestamp. */ class SortByTimestampOperation extends AbstractOperation From 98cf58cbd2f13c50b6f6065c4b7b414b75b95383 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:38:07 +0200 Subject: [PATCH 16/17] TASK: Add `originalCreated` as option --- .../Classes/FlowQueryOperations/SortByTimestampOperation.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php index bd08f3489d4..b49e014694f 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/SortByTimestampOperation.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\NodeAccess\FlowQueryOperations; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\Eel\FlowQuery\FlowQuery; use Neos\Eel\FlowQuery\FlowQueryException; use Neos\Eel\FlowQuery\Operations\AbstractOperation; @@ -46,12 +47,13 @@ public function canEvaluate($context) } /** - * First argument is the timestamp to sort by like created, lastModified, originalLastModified. + * First argument is the timestamp to sort by like created, lastModified, originalCreated and originalLastModified * Second argument is the sort direction (ASC or DESC). * * sortByTimestamp("created", "ASC") * sortByTimestamp("lastModified", "DESC") * + * @see Timestamps for further documentation * * @param FlowQuery $flowQuery the FlowQuery object * @param array $arguments the arguments for this operation. @@ -72,6 +74,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) $timeStamp = match($arguments[0] ?? null) { 'created' => $node->timestamps->created->getTimestamp(), 'lastModified' => $node->timestamps->lastModified?->getTimestamp(), + 'originalCreated' => $node->timestamps->originalCreated->getTimestamp(), 'originalLastModified' => $node->timestamps->originalLastModified?->getTimestamp(), default => throw new FlowQueryException('Please provide a timestamp (created, lastModified, originalLastModified) to sort by.', 1727367726) }; From 08b0ba1dd2a325a6b1ac13c4d7ac7d8c652b4cde Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:07:05 +0200 Subject: [PATCH 17/17] TASK: Test `sortByTimestamp` by manipulating date time --- .../Behavior/Features/Fusion/FlowQuery.feature | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index d0da659a04e..2ab22ae3378 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -51,6 +51,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. | Key | Value | | nodeAggregateId | "root" | | nodeTypeName | "Neos.Neos:Sites" | + Given the current date and time is "2024-09-22T12:00:00+01:00" And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | a | root | Neos.Neos:Site | {"title": "Node a"} | a | @@ -71,6 +72,11 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. | a1b3 | a1b | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b3", "title": "Node a1b3"} | a1b3 | | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "hiddenInMenu": true} | a1c | | a1c1 | a1c | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c1", "title": "Node a1c1"} | a1c1 | + Given the current date and time is "2024-09-22T18:00:00+01:00" + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a2 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: """yaml @@ -400,6 +406,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. """fusion test = Neos.Fusion:DataStructure { @context { + a2 = ${q(site).find('#a2').get(0)} a1a1 = ${q(site).find('#a1a1').get(0)} a1a2 = ${q(site).find('#a1a2').get(0)} a1a3 = ${q(site).find('#a1a3').get(0)} @@ -408,8 +415,8 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. unsorted = ${q([a1a3, a1a4, a1a1, a1a2]).get()} sortByTitleAsc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("title", "ASC").get()} sortByUriDesc = ${q([a1a3, a1a4, a1a1, a1a2]).sort("uriPathSegment", "DESC").get()} - # todo find out how to test time related logic - sortByDateAsc = ${q([a1a1]).sortByTimestamp("created", "ASC").get()} + # a2 is "older" + sortByDateAsc = ${q([a2, a1a1]).sortByTimestamp("created", "ASC").get()} @process.render = Neos.Neos:Test.RenderNodesDataStructure } """ @@ -418,7 +425,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. unsorted: a1a3,a1a4,a1a1,a1a2 sortByTitleAsc: a1a1,a1a2,a1a3,a1a4 sortByUriDesc: a1a4,a1a3,a1a2,a1a1 - sortByDateAsc: a1a1 + sortByDateAsc: a1a1,a2 """ Scenario: Node accessors (final Node access operations)