diff --git a/Classes/Archivist.php b/Classes/Archivist.php index 07fac8c..f99a87a 100644 --- a/Classes/Archivist.php +++ b/Classes/Archivist.php @@ -1,7 +1,13 @@ eelEvaluationService->evaluate($sortingInstructions['affectedNode'], ['node' => $triggeringNode]); + if (!($affectedNode instanceof NodeInterface)) { + $this->logger->log(sprintf('A node of type %s (%s) triggered node organization but the affectedNode was not found.', $triggeringNode->getNodeType()->getName(), $triggeringNode->getIdentifier())); + return; + } + } else { + $affectedNode = $triggeringNode; + } + $this->nodeDataRepository->persistEntities(); - $this->logger->log(sprintf('Organize node of type %s with path %s', $node->getNodeType()->getName(), $node->getPath()), LOG_DEBUG); - $context = $this->buildBaseContext($node, $sortingInstructions); + + $this->logger->log(sprintf('Organize node of type %s with path %s', $triggeringNode->getNodeType()->getName(), $triggeringNode->getPath()), LOG_DEBUG); + $context = $this->buildBaseContext($triggeringNode, $sortingInstructions); if (isset($sortingInstructions['context']) && is_array($sortingInstructions['context'])) { $context = $this->buildCustomContext($context, $sortingInstructions['context']); } if (isset($sortingInstructions['hierarchy']) && is_array($sortingInstructions['hierarchy'])) { - $targetNode = $this->hierarchyService->buildHierarchy($sortingInstructions['hierarchy'], $context); - $node->moveInto($targetNode); - } + $hierarchyNode = $this->hierarchyService->buildHierarchy($sortingInstructions['hierarchy'], $context); + + if($affectedNode->getParent() !== $hierarchyNode) { + $affectedNode->moveInto($hierarchyNode); + $this->logger->log(sprintf('Moved node %s into hierarchy node %s', $affectedNode->getNodeType()->getName(), $hierarchyNode->getNodeType()->getName()), LOG_DEBUG); + } - if (isset($sortingInstructions['sorting'])) { - $this->sortingService->sort($targetNode, $sortingInstructions['sorting'], null); + if (isset($sortingInstructions['sorting'])) { + $this->sortingService->sortChildren($hierarchyNode, $sortingInstructions['sorting'], null); + } } } diff --git a/Classes/Exception/ArchivistConfigurationException.php b/Classes/Exception/ArchivistConfigurationException.php index 95a2487..c4a40e6 100644 --- a/Classes/Exception/ArchivistConfigurationException.php +++ b/Classes/Exception/ArchivistConfigurationException.php @@ -2,8 +2,11 @@ namespace PunktDe\Archivist\Exception; /* - * (c) 2018 punkt.de GmbH - Karlsruhe, Germany - http://punkt.de - * All rights reserved. + * This file is part of the PunktDe.Archivist package. + * + * This package is open source software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. */ use Neos\Flow\Exception; diff --git a/Classes/NodeSignalInterceptor.php b/Classes/NodeSignalInterceptor.php index 7ea0abf..9fbc35c 100644 --- a/Classes/NodeSignalInterceptor.php +++ b/Classes/NodeSignalInterceptor.php @@ -2,6 +2,14 @@ namespace PunktDe\Archivist; +/* + * This file is part of the PunktDe.Archivist package. + * + * This package is open source software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + use Neos\ContentRepository\Domain\Model\NodeInterface; use Neos\Flow\Annotations as Flow; @@ -24,7 +32,7 @@ public function nodeAdded(NodeInterface $node) { return; } - (new Archivist())->sortNode($node, $this->sortingInstructions[$node->getNodeType()->getName()]); + (new Archivist())->organizeNode($node, $this->sortingInstructions[$node->getNodeType()->getName()]); } /** @@ -38,6 +46,6 @@ public function nodePropertyChanged(NodeInterface $node, string $propertyName, $ return; } - (new Archivist())->sortNode($node, $this->sortingInstructions[$node->getNodeType()->getName()]); + (new Archivist())->organizeNode($node, $this->sortingInstructions[$node->getNodeType()->getName()]); } } diff --git a/Classes/Package.php b/Classes/Package.php index d8a74c4..7bf38b8 100644 --- a/Classes/Package.php +++ b/Classes/Package.php @@ -1,6 +1,14 @@ evaluateHierarchyLevelConfiguration($hierarchyLevelConfiguration); $hierarchyLevelNodeType = $this->nodeTypeManager->getNodeType($hierarchyLevelConfiguration['type']); @@ -95,6 +98,10 @@ protected function buildHierarchyLevel(NodeInterface $parentNode, array $hierarc $hierarchyLevelNodeName = (string)$this->eelEvaluationService->evaluateIfValidEelExpression($hierarchyLevelConfiguration['properties']['name'], $context); } + if ($hierarchyLevelNodeName === '') { + return $parentNode; + } + $hierarchyLevelNodeTemplate = new NodeTemplate(); $hierarchyLevelNodeTemplate->setNodeType($hierarchyLevelNodeType); @@ -111,7 +118,7 @@ protected function buildHierarchyLevel(NodeInterface $parentNode, array $hierarc $this->logger->log(sprintf('Built hierarchy level on path %s with node type %s ', $hierarchyLevelNode->getPath(), $hierarchyLevelConfiguration['type']), LOG_DEBUG); if (isset($hierarchyLevelConfiguration['sorting'])) { - $this->sortingService->sort($parentNode, $hierarchyLevelConfiguration['sorting'], $hierarchyLevelNodeType->getName()); + $this->sortingService->sortChildren($parentNode, $hierarchyLevelConfiguration['sorting'], $hierarchyLevelNodeType->getName()); } return $hierarchyLevelNode; diff --git a/Classes/Service/SortingService.php b/Classes/Service/SortingService.php index e261e43..3a0617f 100644 --- a/Classes/Service/SortingService.php +++ b/Classes/Service/SortingService.php @@ -1,7 +1,14 @@ eelEvaluationService->isValidExpression($eelOrProperty)) { $eelExpression = $eelOrProperty; diff --git a/Configuration/Testing/NodeTypes.yaml b/Configuration/Testing/NodeTypes.yaml index 13931ef..3881e7f 100644 --- a/Configuration/Testing/NodeTypes.yaml +++ b/Configuration/Testing/NodeTypes.yaml @@ -16,3 +16,10 @@ type: string date: type: DateTime + +'PunktDe.Archivist.TriggerContentNode': + superTypes: + 'Neos.ContentRepository.Testing:Content': true + properties: + title: + type: string diff --git a/Configuration/Testing/Settings.yaml b/Configuration/Testing/Settings.yaml index eb7941b..3b7dd66 100644 --- a/Configuration/Testing/Settings.yaml +++ b/Configuration/Testing/Settings.yaml @@ -47,16 +47,29 @@ PunktDe: # Simple sorting on a property sorting: title -# # Example triggering content node -# # -# # A content node triggers the move of its parent document node -# 'PunktDe.Archivist.TriggerContentNode': -# -# # The query selecting the root node of the automatically created hierarchy -# hierarchyRoot: "${q(site).find('[instanceof Neos.ContentRepository.Testing:Page]').get(0)}" -# -# # The node to be moved. This defaults to the triggering node and is available as node. -# # This can for example be used if a change in a content node should move its parent document node -# # -# targetNode: "${q(node).parent('[instanceof PunktDe.Archivist.TriggerNode]').get(0)}" + # Example triggering content node + # + # A content node triggers the move of its parent document node. For example, you have a + # title node which should be considered to move the page. + 'PunktDe.Archivist.TriggerContentNode': + + # The query selecting the root node of the automatically created hierarchy + hierarchyRoot: "${q(site).find('[instanceof Neos.ContentRepository.Testing:Page]').get(0)}" + # Optional: The node to be moved, described by an Eel query. + # This defaults to the triggering node if not set. The triggering node is available as "node". + # If the affected node is not found by the operation is skipped. + # This can for example be used if a change in a content node should move its parent document node + # + affectedNode: "${q(node).parent('[instanceof Neos.ContentRepository.Testing:Document]').get(0)}" + + # Definition of the auto-generated hierarchy + hierarchy: + - + # The type of the hierarchy-node + type: 'PunktDe.Archivist.HierarchyNode' + + # Properties of the new created node. + properties: + name: "${String.charAt(node.properties.title, 0)}" + title: "${String.charAt(node.properties.title, 0)}" diff --git a/Readme.md b/Readme.md index 7d9c718..0712528 100644 --- a/Readme.md +++ b/Readme.md @@ -17,30 +17,76 @@ You can configure the behavior differently for every triggering node type. The c are best explained by example. These examples are taken from ``Configuration/Testing/Settings.yaml`` and are thus automatically tested. -## Example Configurations - +### Simple Example + +Configuration for the nodeType 'PunktDe.Archivist.TriggerNode'. The sorting is triggered if a +node of this type is created or if a property on this node is changed. This node is than +available as 'node' in the other parts of the configuration + +PunktDe: + Archivist: + sortingInstructions: + + 'PunktDe.Archivist.TriggerNode': + + # The query selecting the root node of the automatically created hierarchy + hierarchyRoot: "${q(site).find('[instanceof Neos.ContentRepository.Testing:Page]').get(0)}" + + # Optional: The sorting of the nodes inside the target hierarchy. Can be the name of a property + # or an eel expression like seen below + sorting: title + + # In the context is evaluated first. You can define variables here which you can use in + # the remaining configuration + context: + publishDate: "${node.properties.date}" + + # Definition of the auto-generated hierarchy + hierarchy: + - + # The type of the hierarchy-node + type: 'PunktDe.Archivist.HierarchyNode' + + # Properties of the new created node. + properties: + name: "${Date.year(publishDate)}" + title: "${Date.year(publishDate)}" + + # The property which is identical throughout all nodes of this level + identity: title + + # An eel query that describes the sorting condition + sorting: "${q(a).property('title') < q(b).property('title')}" + - + type: 'PunktDe.Archivist.HierarchyNode' + properties: + name: "${Date.month(publishDate)}" + title: "${Date.month(publishDate)}" + identity: title + + # Simple sorting on a property + sorting: title + + +### Example with a triggering content node + +A content node triggers the move of its parent document node. For example, if you have a +title node which should be considered to move the page. + PunktDe: Archivist: sortingInstructions: - - # Simple Example - # - # Configuration for the nodeType 'PunktDe.Archivist.TriggerNode'. The sorting is triggered if a - # node of this type is created or if a property on this node is changed. This node is than - # available as 'node' in the other parts of the configuration - 'PunktDe.Archivist.TriggerNode': + 'PunktDe.Archivist.TriggerContentNode': # The query selecting the root node of the automatically created hierarchy hierarchyRoot: "${q(site).find('[instanceof Neos.ContentRepository.Testing:Page]').get(0)}" - # Optional: The sorting of the nodes inside the target hierarchy. Can be the name of a property - # or an eel expression like seen below - sorting: title - - # In the context is evaluated first. You can define variables here which you can use in - # the remaining configuration - context: - publishDate: "${node.properties.date}" + # Optional: The node to be moved, described by an Eel query. + # This defaults to the triggering node if not set. The triggering node is available as "node". + # If the affected node is not found by the operation is skipped. + # This can for example be used if a change in a content node should move its parent document node + # + affectedNode: "${q(node).parent('[instanceof Neos.ContentRepository.Testing:Document]').get(0)}" # Definition of the auto-generated hierarchy hierarchy: @@ -50,20 +96,5 @@ and are thus automatically tested. # Properties of the new created node. properties: - name: "${Date.year(publishDate)}" - title: "${Date.year(publishDate)}" - - # The property which is identical throughout all nodes of this level - identity: title - - # An eel query that describes the sorting condition - sorting: "${q(a).property('title') < q(b).property('title')}" - - - type: 'PunktDe.Archivist.HierarchyNode' - properties: - name: "${Date.month(publishDate)}" - title: "${Date.month(publishDate)}" - identity: title - - # Simple sorting on a property - sorting: title + name: "${String.charAt(node.properties.title, 0)}" + title: "${String.charAt(node.properties.title, 0)}" diff --git a/Tests/Functional/ArchivistTest.php b/Tests/Functional/ArchivistTest.php index a63834a..b9730c6 100644 --- a/Tests/Functional/ArchivistTest.php +++ b/Tests/Functional/ArchivistTest.php @@ -1,7 +1,14 @@ nodeTypeManager = $this->objectManager->get(NodeTypeManager::class); + } + /** * @test */ - public function nodeStructureIsAvailable() { + public function nodeStructureIsAvailable() + { $this->assertEquals('Neos.ContentRepository.Testing:Page', $this->node->getNodeType()->getName()); } /** * @test */ - public function createNode() + public function simpleCreateNode() { - $newNode = $this->triggerNodeCreation(); + $newNode = $this->createNode('trigger-node', ['title' => 'New Article', 'date' => new \DateTime('2018-01-19')]); // The hierarchy is created $lvl1 = $this->node->getChildNodes('PunktDe.Archivist.HierarchyNode')[0]; @@ -44,8 +63,8 @@ public function createNode() */ public function hierarchyIsNotCreatedTwice() { - $this->triggerNodeCreation('trigger-node1'); - $this->triggerNodeCreation('trigger-node2'); + $this->createNode('trigger-node1', ['title' => 'New Article', 'date' => new \DateTime('2018-01-19')]); + $this->createNode('trigger-node2', ['title' => 'New Article', 'date' => new \DateTime('2018-01-19')]); $this->assertCount(1, $this->node->getChildNodes('PunktDe.Archivist.HierarchyNode')); } @@ -53,9 +72,10 @@ public function hierarchyIsNotCreatedTwice() /** * @test */ - public function hierarchyNodesAreSortedCorrectlyWithSimpleProperty() { - $this->triggerNodeCreation('trigger-node1', ['date' => new \DateTime('2018-01-20')]); - $this->triggerNodeCreation('trigger-node2', ['date' => new \DateTime('2017-01-19')]); + public function hierarchyNodesAreSortedCorrectlyWithSimpleProperty() + { + $this->createNode('trigger-node1', ['title' => 'New Article', 'date' => new \DateTime('2018-01-20')]); + $this->createNode('trigger-node2', ['title' => 'New Article', 'date' => new \DateTime('2017-01-19')]); $yearNodes = $this->node->getChildNodes('PunktDe.Archivist.HierarchyNode'); $this->assertEquals('2017', $yearNodes[0]->getProperty('title')); @@ -65,9 +85,10 @@ public function hierarchyNodesAreSortedCorrectlyWithSimpleProperty() { /** * @test */ - public function hierarchyNodesAreSortedCorrectlyWithEelExpression() { - $this->triggerNodeCreation('trigger-node1', ['date' => new \DateTime('2018-02-20')]); - $this->triggerNodeCreation('trigger-node2', ['date' => new \DateTime('2016-01-19')]); + public function hierarchyNodesAreSortedCorrectlyWithEelExpression() + { + $this->createNode('trigger-node1', ['title' => 'New Article', 'date' => new \DateTime('2018-02-20')]); + $this->createNode('trigger-node2', ['title' => 'New Article', 'date' => new \DateTime('2016-01-19')]); $monthNodes = (new FlowQuery([$this->node]))->children('[instanceof PunktDe.Archivist.HierarchyNode]')->children('[instanceof PunktDe.Archivist.HierarchyNode]')->get(); $this->assertEquals('1', $monthNodes[0]->getProperty('title')); @@ -77,9 +98,10 @@ public function hierarchyNodesAreSortedCorrectlyWithEelExpression() { /** * @test */ - public function createdNodesAreSortedCorrectly() { - $this->triggerNodeCreation('trigger-node2', ['title' => 'Node 2']); - $this->triggerNodeCreation('trigger-node1', ['title' => 'Node 1']); + public function createdNodesAreSortedCorrectly() + { + $this->createNode('trigger-node2', ['title' => 'Node 2', 'date' => new \DateTime('2018-01-19')]); + $this->createNode('trigger-node1', ['title' => 'Node 1', 'date' => new \DateTime('2018-01-19')]); $triggerNodes = (new FlowQuery([$this->node]))->find('[instanceof PunktDe.Archivist.TriggerNode]')->get(); @@ -90,9 +112,10 @@ public function createdNodesAreSortedCorrectly() { /** * @test */ - public function nodesAreSortedIfHierarchyAlreadyExist() { - $triggerNode2 = $this->triggerNodeCreation('trigger-node2', ['title' => 'Node 2']); - $triggerNode1 = $this->triggerNodeCreation('trigger-node1', ['title' => 'Node 1']); + public function nodesAreSortedIfHierarchyAlreadyExist() + { + $triggerNode2 = $this->createNode('trigger-node2', ['title' => 'Node 2', 'date' => new \DateTime('2018-01-19')]); + $triggerNode1 = $this->createNode('trigger-node1', ['title' => 'Node 1', 'date' => new \DateTime('2018-01-19')]); $yearNode = (new FlowQuery([$this->node]))->children('[instanceof PunktDe.Archivist.HierarchyNode]')->get(0); $this->assertEquals('2018', $yearNode->getProperty('title')); @@ -110,8 +133,9 @@ public function nodesAreSortedIfHierarchyAlreadyExist() { /** * @test */ - public function changedPropertyTriggersNodeReSorting() { - $triggerNode = $this->triggerNodeCreation('trigger-node', ['title' => 'Node 1']); + public function changedPropertyTriggersNodeReSorting() + { + $triggerNode = $this->createNode('trigger-node', ['title' => 'Node 1', 'date' => new \DateTime('2018-01-19')]); $expectedPath = $this->node->getPath() . '/2018/1/trigger-node'; $this->assertEquals($expectedPath, $triggerNode->getPath()); @@ -120,6 +144,19 @@ public function changedPropertyTriggersNodeReSorting() { $this->assertEquals($expectedPath, $triggerNode->getPath()); } + /** + * @test + */ + public function documentNodeIsSortedByTriggeringContentNode() + { + $affectedDocumentNode = $this->createNode('affected-node', ['title' => 'theTitle'], 'Neos.ContentRepository.Testing:Document'); + $triggerNode = $affectedDocumentNode->createNode('trigger-node', $this->nodeTypeManager->getNodeType('PunktDe.Archivist.TriggerContentNode')); + $triggerNode->setProperty('title', 'ABC'); + $this->assertEquals('A', $affectedDocumentNode->getParent()->getProperty('title')); + + $triggerNode->setProperty('title', 'BCD'); + $this->assertEquals('B', $affectedDocumentNode->getParent()->getProperty('title')); + } /** * @param string $nodeName @@ -127,17 +164,9 @@ public function changedPropertyTriggersNodeReSorting() { * @param string $triggerNodeType * @return \Neos\ContentRepository\Domain\Model\NodeInterface */ - protected function triggerNodeCreation($nodeName = 'trigger-node', array $properties = [], $triggerNodeType = 'PunktDe.Archivist.TriggerNode') + protected function createNode($nodeName = 'trigger-node', array $properties = [], $triggerNodeType = 'PunktDe.Archivist.TriggerNode') { - $defaultProperties = [ - 'title' => 'New Article', - 'date' => new \DateTime('2018-01-19') - ]; - - $properties = array_merge($defaultProperties, $properties); - - $nodeTypeManager = $this->objectManager->get(NodeTypeManager::class); - $triggerNodeType = $nodeTypeManager->getNodeType($triggerNodeType); + $triggerNodeType = $this->nodeTypeManager->getNodeType($triggerNodeType); $triggerNodeTemplate = new NodeTemplate(); $triggerNodeTemplate->setNodeType($triggerNodeType);