Skip to content
This repository has been archived by the owner on Sep 23, 2020. It is now read-only.

Added recursive restore to allow also versions of children #108

Merged
merged 2 commits into from
Apr 19, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 64 additions & 16 deletions lib/Subscriber/Behavior/VersionSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -226,26 +229,71 @@ 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)) {
$property->remove();
}
}

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 do 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();
}
}

Expand Down
133 changes: 133 additions & 0 deletions tests/Unit/Subscriber/Behavior/VersionSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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([]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add also a test where the node has children, which should not be copied (as it is the case with the pages).


$property1->remove()->shouldBeCalled();
$property2->remove()->shouldBeCalled();
Expand All @@ -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',
Expand All @@ -372,4 +376,133 @@ 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());
}

public function testRestoreIgnoreChildren()
{
$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('child')->willReturn(true);

$definition = $this->prophesize(NodeDefinitionInterface::class);
$definition->getOnParentVersion()->willReturn(OnParentVersionAction::IGNORE);

$childNode = $this->prophesize(NodeInterface::class);
$childNode->getName()->willReturn('child');
$childNode->getProperties()->willReturn([]);
$childNode->getNodes()->willReturn([]);
$childNode->getDefinition()->willReturn($definition->reveal());

$node->getNode('child')->willReturn($childNode->reveal());
$node->getNodes()->willReturn([$childNode->reveal()]);

$this->propertyEncoder->localizedContentName('', 'de')->willReturn('i18n:de-');
$this->propertyEncoder->localizedSystemName('', 'de')->willReturn('i18n:de-');

$frozenNode->getNodes()->willReturn([]);
$frozenNode->getPropertiesValues()->willReturn([]);
$frozenNode->hasNode(Argument::type('string'))->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());

// the child-node should not be touched
Copy link
Member Author

Choose a reason for hiding this comment

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

@danrot i have added assertions

$childNode->remove()->shouldNotBeCalled();
$childNode->setProperty(Argument::cetera())->shouldNotBeCalled();

$this->versionSubscriber->restoreProperties($event->reveal());
Copy link
Contributor

Choose a reason for hiding this comment

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

There's not a single assertion in this test, so we cannot be sure that the child node has not been touched...

Copy link
Member Author

Choose a reason for hiding this comment

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

it's not explicit but we could. every not mocked method call would let this test fail - but i will add some assertions.

}
}