diff --git a/CHANGELOG.md b/CHANGELOG.md index 766cc2c..d725ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG for Sulu Document Manager * dev-develop * ENHANCEMENT #110 Added node-name-slugifier to centralice additional node name replacer * ENHANCEMENT #109 Added metadata to configure remove-live + * FEATURE #107 Added recursive restore to allow also versions of children * 0.9.1 (2017-03-16) * ENHANCEMENT #107 Added VersionNotFoundException diff --git a/lib/Subscriber/Behavior/VersionSubscriber.php b/lib/Subscriber/Behavior/VersionSubscriber.php index 3e611d3..1d92c98 100644 --- a/lib/Subscriber/Behavior/VersionSubscriber.php +++ b/lib/Subscriber/Behavior/VersionSubscriber.php @@ -14,6 +14,7 @@ use Jackalope\Version\VersionManager; use PHPCR\NodeInterface; use PHPCR\SessionInterface; +use PHPCR\Version\OnParentVersionAction; use PHPCR\Version\VersionException; use Sulu\Component\DocumentManager\Behavior\VersionBehavior; use Sulu\Component\DocumentManager\Event\AbstractMappingEvent; @@ -190,12 +191,14 @@ public function applyVersionOperations() if (!array_key_exists($versionInformation['path'], $nodeVersions)) { $nodeVersions[$versionInformation['path']] = $versions; } - $nodeVersions[$versionInformation['path']][] = json_encode([ - 'locale' => $versionInformation['locale'], - 'version' => $version->getName(), - 'author' => $versionInformation['author'], - 'authored' => date('c'), - ]); + $nodeVersions[$versionInformation['path']][] = json_encode( + [ + 'locale' => $versionInformation['locale'], + 'version' => $version->getName(), + 'author' => $versionInformation['author'], + 'authored' => date('c'), + ] + ); } foreach ($nodes as $path => $node) { @@ -226,6 +229,32 @@ public function restoreProperties(RestoreEvent $event) $node = $event->getNode(); + try { + $version = $this->versionManager->getVersionHistory($node->getPath())->getVersion($event->getVersion()); + + $frozenNode = $version->getFrozenNode(); + + $this->restoreNode($node, $frozenNode, $contentPropertyPrefix, $systemPropertyPrefix); + } catch (VersionException $exception) { + throw new VersionNotFoundException($event->getDocument(), $event->getVersion()); + } + } + + /** + * Restore given node with properties given from frozen-node. + * Will be called recursive. + * + * @param NodeInterface $node + * @param NodeInterface $frozenNode + * @param string $contentPropertyPrefix + * @param string $systemPropertyPrefix + */ + private function restoreNode( + NodeInterface $node, + NodeInterface $frozenNode, + $contentPropertyPrefix, + $systemPropertyPrefix + ) { // remove the properties for the given language, so that values being added since the last version are removed foreach ($node->getProperties() as $property) { if ($this->isRestoreProperty($property->getName(), $contentPropertyPrefix, $systemPropertyPrefix)) { @@ -233,19 +262,38 @@ public function restoreProperties(RestoreEvent $event) } } - try { - $version = $this->versionManager->getVersionHistory($node->getPath())->getVersion($event->getVersion()); + // set all the properties from the saved version to the node + foreach ($frozenNode->getPropertiesValues() as $name => $value) { + if ($this->isRestoreProperty($name, $contentPropertyPrefix, $systemPropertyPrefix)) { + $node->setProperty($name, $value); + } + } - $frozenNode = $version->getFrozenNode(); + /** @var NodeInterface $childNode */ + foreach ($frozenNode->getNodes() as $childNode) { + // create new node if it not exists + if (!$node->hasNode($childNode->getName())) { + $newNode = $node->addNode($childNode->getName()); + $newNode->setMixins($childNode->getPropertyValueWithDefault('jcr:frozenMixinTypes', [])); + } - // set all the properties from the saved version to the node - foreach ($frozenNode->getPropertiesValues() as $name => $value) { - if ($this->isRestoreProperty($name, $contentPropertyPrefix, $systemPropertyPrefix)) { - $node->setProperty($name, $value); - } + $this->restoreNode( + $node->getNode($childNode->getName()), + $childNode, + $contentPropertyPrefix, + $systemPropertyPrefix + ); + } + + // remove child-nodes which does not exists in frozen-node + foreach ($node->getNodes() as $childNode) { + if ($childNode->getDefinition()->getOnParentVersion() !== OnParentVersionAction::COPY + || $frozenNode->hasNode($childNode->getName()) + ) { + continue; } - } catch (VersionException $exception) { - throw new VersionNotFoundException($event->getDocument(), $event->getVersion()); + + $childNode->remove(); } } diff --git a/tests/Unit/Subscriber/Behavior/VersionSubscriberTest.php b/tests/Unit/Subscriber/Behavior/VersionSubscriberTest.php index 4d1b04b..878bdb0 100644 --- a/tests/Unit/Subscriber/Behavior/VersionSubscriberTest.php +++ b/tests/Unit/Subscriber/Behavior/VersionSubscriberTest.php @@ -14,8 +14,10 @@ use Jackalope\Version\Version as JackalopeVersion; use Jackalope\Workspace; use PHPCR\NodeInterface; +use PHPCR\NodeType\NodeDefinitionInterface; use PHPCR\PropertyInterface; use PHPCR\SessionInterface; +use PHPCR\Version\OnParentVersionAction; use PHPCR\Version\VersionHistoryInterface; use PHPCR\Version\VersionInterface; use PHPCR\Version\VersionManagerInterface; @@ -343,6 +345,7 @@ public function testRestoreLocalizedProperties() $property3 = $this->prophesize(PropertyInterface::class); $property3->getName()->willReturn('jcr:uuid'); $node->getProperties()->willReturn([$property1->reveal(), $property2->reveal(), $property3->reveal()]); + $node->getNodes()->willReturn([]); $property1->remove()->shouldBeCalled(); $property2->remove()->shouldBeCalled(); @@ -351,6 +354,7 @@ public function testRestoreLocalizedProperties() $this->propertyEncoder->localizedContentName('', 'de')->willReturn('i18n:de-'); $this->propertyEncoder->localizedSystemName('', 'de')->willReturn('i18n:de-'); + $frozenNode->getNodes()->willReturn([]); $frozenNode->getPropertiesValues()->willReturn([ 'i18n:de-test' => 'Title', 'non-translatable-test' => 'Article', @@ -372,4 +376,85 @@ public function testRestoreLocalizedProperties() $this->versionSubscriber->restoreProperties($event->reveal()); } + + public function testRestoreChildren() + { + $event = $this->prophesize(RestoreEvent::class); + $document = $this->prophesize(VersionBehavior::class); + $node = $this->prophesize(NodeInterface::class); + $versionHistory = $this->prophesize(VersionHistoryInterface::class); + $version = $this->prophesize(JackalopeVersion::class); + $frozenNode = $this->prophesize(NodeInterface::class); + + $node->getPath()->willReturn('/node'); + $node->getProperties()->willReturn([]); + $node->hasNode('child1')->willReturn(false); + $node->hasNode('child2')->willReturn(true); + $node->hasNode('child3')->willReturn(true); + + $definition = $this->prophesize(NodeDefinitionInterface::class); + $definition->getOnParentVersion()->willReturn(OnParentVersionAction::COPY); + + $newChild2Node = $this->prophesize(NodeInterface::class); + $newChild2Node->getName()->willReturn('child2'); + $newChild2Node->getProperties()->willReturn([]); + $newChild2Node->getNodes()->willReturn([]); + $newChild2Node->getDefinition()->willReturn($definition->reveal()); + + $newChild3Node = $this->prophesize(NodeInterface::class); + $newChild3Node->getName()->willReturn('child3'); + $newChild3Node->getDefinition()->willReturn($definition->reveal()); + $newChild3Node->remove()->shouldBeCalled(); + + $newChild1Node = $this->prophesize(NodeInterface::class); + $newChild1Node->getName()->willReturn('child1'); + $newChild1Node->setMixins(['jcr:referencable'])->shouldBeCalled(); + $newChild1Node->getProperties()->willReturn([]); + $newChild1Node->getNodes()->willReturn([]); + $newChild1Node->getDefinition()->willReturn($definition->reveal()); + $node->addNode('child1')->will( + function () use ($node, $newChild1Node, $newChild2Node, $newChild3Node) { + $node->getNode('child1')->willReturn($newChild1Node->reveal()); + $node->getNodes()->willReturn( + [$newChild1Node->reveal(), $newChild2Node->reveal(), $newChild3Node->reveal()] + ); + + return $newChild1Node->reveal(); + } + ); + + $node->getNode('child2')->willReturn($newChild2Node->reveal()); + $node->getNode('child3')->willReturn($newChild3Node->reveal()); + $node->getNodes()->willReturn([$newChild2Node->reveal(), $newChild3Node->reveal()]); + + $this->propertyEncoder->localizedContentName('', 'de')->willReturn('i18n:de-'); + $this->propertyEncoder->localizedSystemName('', 'de')->willReturn('i18n:de-'); + + $child1 = $this->prophesize(NodeInterface::class); + $child1->getName()->willReturn('child1'); + $child1->getPropertyValueWithDefault('jcr:frozenMixinTypes', [])->willReturn(['jcr:referencable']); + $child1->getPropertiesValues()->willReturn([]); + $child1->getNodes()->willReturn([]); + $child2 = $this->prophesize(NodeInterface::class); + $child2->getName()->willReturn('child2'); + $child2->getPropertiesValues()->willReturn([]); + $child2->getNodes()->willReturn([]); + + $frozenNode->getNodes()->willReturn([$child1->reveal(), $child2->reveal()]); + $frozenNode->getPropertiesValues()->willReturn([]); + $frozenNode->hasNode('child1')->willReturn(true); + $frozenNode->hasNode('child2')->willReturn(true); + $frozenNode->hasNode('child3')->willReturn(false); + + $event->getDocument()->willReturn($document->reveal()); + $event->getNode()->willReturn($node->reveal()); + $event->getVersion()->willReturn('1.0'); + $event->getLocale()->willReturn('de'); + + $this->versionManager->getVersionHistory('/node')->willReturn($versionHistory->reveal()); + $versionHistory->getVersion('1.0')->willReturn($version->reveal()); + $version->getFrozenNode()->willReturn($frozenNode->reveal()); + + $this->versionSubscriber->restoreProperties($event->reveal()); + } }