diff --git a/composer.json b/composer.json index 9297c9f9..d47983ba 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ "php": ">=5.3.3", "doctrine/dbal": ">=2.4.5,<3.0.x-dev", "phpcr/phpcr": "~2.1.2", - "phpcr/phpcr-utils": "^1.2.8", - "jackalope/jackalope": "~1.2.6" + "phpcr/phpcr-utils": "dev-versioning as 1.2.9", + "jackalope/jackalope": "dev-versioning as 1.3.0" }, "provide": { "jackalope/jackalope-transport": "1.1.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d47021c7..2a8057e1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -47,9 +47,6 @@ - vendor/phpcr/phpcr/src/PHPCR/Version - vendor/jackalope/jackalope/src/Jackalope/Version - vendor/phpcr/phpcr/src/PHPCR/Lock vendor/jackalope/jackalope/src/Jackalope/Lock diff --git a/src/Jackalope/Transport/DoctrineDBAL/Client.php b/src/Jackalope/Transport/DoctrineDBAL/Client.php index bed791ce..a7577fa9 100644 --- a/src/Jackalope/Transport/DoctrineDBAL/Client.php +++ b/src/Jackalope/Transport/DoctrineDBAL/Client.php @@ -2,6 +2,8 @@ namespace Jackalope\Transport\DoctrineDBAL; +use Jackalope\Version\GenericVersioningInterface; +use Jackalope\Version\VersionHandler; use PHPCR\LoginException; use PHPCR\NodeType\NodeDefinitionInterface; use PHPCR\NodeType\NodeTypeExistsException; @@ -23,6 +25,7 @@ use PHPCR\PathNotFoundException; use PHPCR\Query\InvalidQueryException; use PHPCR\NodeType\ConstraintViolationException; +use PHPCR\UnsupportedRepositoryOperationException; use PHPCR\Util\QOM\Sql2ToQomQueryConverter; use PHPCR\Util\ValueConverter; use PHPCR\Util\UUIDHelper; @@ -48,6 +51,7 @@ use Jackalope\FactoryInterface; use Jackalope\NotImplementedException; use Jackalope\NodeType\NodeProcessor; +use PHPCR\Version\VersionException; /** * Class to handle the communication between Jackalope and RDBMS via Doctrine DBAL. @@ -58,7 +62,7 @@ * @author Benjamin Eberlei * @author Lukas Kahwe Smith */ -class Client extends BaseTransport implements QueryTransport, WritingInterface, WorkspaceManagementInterface, NodeTypeManagementInterface, TransactionInterface +class Client extends BaseTransport implements QueryTransport, WritingInterface, WorkspaceManagementInterface, NodeTypeManagementInterface, TransactionInterface, GenericVersioningInterface { /** * SQlite can only handle a maximum of 999 parameters inside an IN statement @@ -189,6 +193,11 @@ class Client extends BaseTransport implements QueryTransport, WritingInterface, */ private $nodeProcessor; + /** + * @var VersionHandler + */ + private $versionHandler; + /** * @param FactoryInterface $factory * @param Connection $conn @@ -1899,7 +1908,8 @@ private function getNodeProcessor() $this->nodeProcessor = new NodeProcessor( $this->credentials->getUserID(), $this->getNamespacesObject(), - $this->getAutoLastModified() + $this->getAutoLastModified(), + $this->versionHandler ); return $this->nodeProcessor; @@ -2588,8 +2598,12 @@ public function rollbackSave() public function updateProperties(Node $node) { $this->assertLoggedIn(); - // we can ignore the operations returned, there will be no additions because of property updates - $this->getNodeProcessor()->process($node); + + $additionalAddOperations = $this->getNodeProcessor()->process($node); + + if (!empty($additionalAddOperations)) { + $this->storeNodes($additionalAddOperations); + } $this->syncNode($node->getIdentifier(), $node->getPath(), $node->getPrimaryNodeType(), false, $node->getProperties()); @@ -2617,4 +2631,49 @@ private function initConnection() $this->connectionInitialized = true; } + + /** + * {@inheritDoc} + */ + public function checkinItem($path) + { + return $this->versionHandler->checkinItem($path); + } + + /** + * {@inheritDoc} + */ + public function checkoutItem($path) + { + return $this->versionHandler->checkoutItem($path); + } + + /** + * {@inheritDoc} + */ + public function restoreItem($removeExisting, $versionPath, $path) + { + throw new NotImplementedException(); + } + + /** + * {@inheritDoc} + */ + public function removeVersion($versionPath, $versionName) + { + throw new NotImplementedException(); + } + + /** + * Sets the generic version handler delivered by jackalope + * @param VersionHandler $versionHandler + */ + public function setVersionHandler(VersionHandler $versionHandler) + { + if ($this->versionHandler) { + throw new \InvalidArgumentException('Version handler is already set'); + } + + $this->versionHandler = $versionHandler; + } } diff --git a/tests/ImplementationLoader.php b/tests/ImplementationLoader.php index 38c81f03..82c1cca8 100644 --- a/tests/ImplementationLoader.php +++ b/tests/ImplementationLoader.php @@ -46,7 +46,6 @@ protected function __construct(Connection $connection, $fixturePath) 'SameNameSiblings', //TODO: Not implemented, no test currently written for it 'PermissionsAndCapabilities', //TODO: Transport does not support permissions 'Observation', //TODO: Transport does not support observation - 'Versioning', //TODO: Transport does not support versioning 'Locking', //TODO: Transport does not support locking ); @@ -82,7 +81,16 @@ protected function __construct(Connection $connection, $fixturePath) // TODO: implement creating workspace with source 'WorkspaceManagement\\WorkspaceManagementTest::testCreateWorkspaceWithSource', - 'WorkspaceManagement\\WorkspaceManagementTest::testCreateWorkspaceWithInvalidSource' + 'WorkspaceManagement\\WorkspaceManagementTest::testCreateWorkspaceWithInvalidSource', + + // TODO: implement remove version + 'Versioning\\VersionHistoryTest::testDeleteVersion', + 'Versioning\\VersionHistoryTest::testDeleteLatestVersion', + 'Versioning\\VersionHistoryTest::testDeleteUnexistingVersion', + 'Versioning\\VersionManagerTest::testRestoreByPathAndName', + 'Versioning\\VersionManagerTest::testRestoreByVersionObject', + 'Versioning\\VersionManagerTest::testRestoreRootVersion', + 'Versioning\\VersionManagerTest::testWriteNotCheckedOutVersion', ); if ($connection->getDatabasePlatform() instanceof Doctrine\DBAL\Platforms\SqlitePlatform) { diff --git a/tests/Jackalope/Test/Fixture/DBUnitFixtureXML.php b/tests/Jackalope/Test/Fixture/DBUnitFixtureXML.php index db93f757..17eb6b57 100644 --- a/tests/Jackalope/Test/Fixture/DBUnitFixtureXML.php +++ b/tests/Jackalope/Test/Fixture/DBUnitFixtureXML.php @@ -40,6 +40,12 @@ class DBUnitFixtureXML extends XMLDocument */ protected $expectedNodes; + /** + * Track if root nodes exists for workspace names + * @var array + */ + private $rootNodes; + /** * @param string $file - file path * @param int $options - libxml option constants: http://www.php.net/manual/en/libxml.constants.php @@ -48,10 +54,10 @@ public function __construct($file, $options = null) { parent::__construct($file, $options); - $this->tables = array(); - $this->ids = array(); - $this->references = array(); - $this->expectedNodes = array(); + $this->tables = array(); + $this->ids = array(); + $this->references = array(); + $this->expectedNodes = array(); } public function addDataset() @@ -101,9 +107,18 @@ public function addNamespaces(array $namespaces) public function addNodes($workspaceName, \DOMNodeList $nodes) { $node = $nodes->item(0); - if ('jcr:root' !== $node->getAttributeNS($this->namespaces['sv'], 'name')) { - $this->addRootNode('tests'); + if (!isset($this->rootNodes[$workspaceName])) { + if ('jcr:root' !== $node->getAttributeNS($this->namespaces['sv'], 'name')) { + $this->addRootNode('tests'); + } + $this->rootNodes[$workspaceName] = true; + } + + $srcDom = new \Jackalope\Test\Fixture\JCRSystemXML(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . 'system.xml'); + foreach ($srcDom->load()->getNodes() as $node) { + $this->addNode($workspaceName, $node); } + foreach ($nodes as $node) { $this->addNode($workspaceName, $node); } @@ -117,31 +132,31 @@ public function addRootNode($workspaceName = 'default') $this->ids[$uuid] = self::$idCounter++; return $this->addRow('phpcr_nodes', array( - 'id' => $this->ids[$uuid], - 'path' => '/', - 'parent' => '', - 'local_name' => '', - 'namespace' => '', - 'workspace_name'=> $workspaceName, - 'identifier' => $uuid, - 'type' => 'nt:unstructured', - 'props' => '' - . '', - 'depth' => 0, - 'sort_order' => 0, + 'id' => $this->ids[$uuid], + 'path' => '/', + 'parent' => '', + 'local_name' => '', + 'namespace' => '', + 'workspace_name' => $workspaceName, + 'identifier' => $uuid, + 'type' => 'nt:unstructured', + 'props' => '' + . '', + 'depth' => 0, + 'sort_order' => 0, )); } @@ -174,8 +189,8 @@ public function addNode($workspaceName, \DOMElement $node) list($parentPath, $childPath) = $this->getPath($node); - $namespace = ''; - $name = $node->getAttributeNS($this->namespaces['sv'], 'name'); + $namespace = ''; + $name = $node->getAttributeNS($this->namespaces['sv'], 'name'); if (count($parts = explode(':', $name, 2)) == 2) { list($namespace, $name) = $parts; } @@ -188,18 +203,24 @@ public function addNode($workspaceName, \DOMElement $node) $namespace = ''; } + if (isset($properties['jcr:mixinTypes']) + && in_array('mix:versionable', $properties['jcr:mixinTypes']['value']) + ) { + $this->addVersioningProperties($dom, $phpcrNode, $workspaceName, $id, $uuid); + } + $this->addRow('phpcr_nodes', array( - 'id' => $id, - 'path' => $childPath, - 'parent' => $parentPath, - 'local_name' => $name, - 'namespace' => $namespace, - 'workspace_name'=> $workspaceName, - 'identifier' => $uuid, - 'type' => $properties['jcr:primaryType']['value'][0], - 'props' => $dom->saveXML(), - 'depth' => PathHelper::getPathDepth($childPath), - 'sort_order' => $id - 2, + 'id' => $id, + 'path' => $childPath, + 'parent' => $parentPath, + 'local_name' => $name, + 'namespace' => $namespace, + 'workspace_name' => $workspaceName, + 'identifier' => $uuid, + 'type' => $properties['jcr:primaryType']['value'][0], + 'props' => $dom->saveXML(), + 'depth' => PathHelper::getPathDepth($childPath), + 'sort_order' => $id - 2, )); return $this; @@ -261,7 +282,7 @@ public function getChildAttribute(\DOMElement $node) $isMultiValue = true; } - return array($name, array('type' => $type, 'value' => $values, 'multiValued' => $isMultiValue)); + return array($name, array('type' => $type, 'value' => $values, 'multiValued' => $isMultiValue)); } public function createPropertyNode($workspaceName, $propertyName, $propertyData, $id, \DOMDocument $dom) @@ -308,9 +329,9 @@ public function createValueNodeByType($workspaceName, $type, $value, $id, $prope } // do not repeat references $this->references[$type][$value][$id . $propertyName . $targetId] = array( - 'source_id' => $id, - 'source_property_name' => $propertyName, - 'target_id' => $targetId, + 'source_id' => $id, + 'source_property_name' => $propertyName, + 'target_id' => $targetId, ); break; } @@ -339,7 +360,7 @@ public function createValueNode($value, \DOMDocument $dom, $length) public function getPath(\DOMElement $node) { - $childPath = ''; + $childPath = ''; $parent = $node; do { @@ -371,11 +392,11 @@ public function addBinaryNode($id, $propertyName, $workspaceName, $idx, $data) $data = base64_decode($data); $this->addRow('phpcr_binarydata', array( - 'node_id' => $id, - 'property_name' => $propertyName, - 'workspace_name' => $workspaceName, - 'idx' => $idx, - 'data' => $data, + 'node_id' => $id, + 'property_name' => $propertyName, + 'workspace_name' => $workspaceName, + 'idx' => $idx, + 'data' => $data, )); return strlen($data); @@ -413,4 +434,137 @@ protected function ensureTableExists($tableName, $columns) return $this; } + + private function addVersioningProperties(\DOMDocument $dom, \DOMElement $node, $workspaceName, $id, $uuid) + { + $node->appendChild( + $this->createPropertyNode( + $workspaceName, + 'jcr:isCheckedOut', + array('type' => 'boolean', 'value' => array('true'), 'multiValued' => false), + $id, + $dom + ) + ); + + // version node in version storage + $versionNodeUuid = UUIDHelper::generateUUID(); + $versionParentPath = '/jcr:system/jcr:versionStorage'; + $versionPath = $versionParentPath . '/' . $versionNodeUuid; + $this->addRow( + 'phpcr_nodes', + array( + 'id' => self::$idCounter++, + 'path' => $versionPath, + 'parent' => $versionParentPath, + 'local_name' => $versionNodeUuid, + 'namespace' => '', + 'workspace_name' => $workspaceName, + 'identifier' => $versionNodeUuid, + 'type' => 'nt:unstructured', + 'props' => '' + . '' + . '' + . '' . $versionNodeUuid . '' + . '' + . '' + . '' . $uuid . '' + . '' + . '', + 'depth' => PathHelper::getPathDepth($versionPath), + 'sort_order' => $id - 2, + ) + ); + + $this->addRow( + 'phpcr_nodes_references', + array( + 'source_id' => $id, + 'source_property_name' => 'jcr:versionHistory', + 'target_id' => self::$idCounter - 1 + ) + ); + + $node->appendChild( + $this->createPropertyNode( + $workspaceName, + 'jcr:versionHistory', + array('type' => 'reference', 'value' => array($versionNodeUuid), 'multiValued' => false), + $id, + $dom + ) + ); + + // root version + $rootVersionPath = $versionPath . '/jcr:rootVersion'; + $rootVersionUuid = UUIDHelper::generateUUID(); + $this->addRow( + 'phpcr_nodes', + array( + 'id' => self::$idCounter++, + 'path' => $rootVersionPath, + 'parent' => $versionPath, + 'local_name' => 'jcr:rootVersion', + 'namespace' => '', + 'workspace_name' => $workspaceName, + 'identifier' => $rootVersionUuid, + 'type' => 'nt:version', + 'props' => '' + . '' + . '' + . '' . $rootVersionUuid . '' + . '' + . '' + . '' + . '', + 'depth' => PathHelper::getPathDepth($rootVersionPath), + 'sort_order' => $id - 2, + ) + ); + + $node->appendChild( + $this->createPropertyNode( + $workspaceName, + 'jcr:baseVersion', + array('type' => 'reference', 'value' => array($rootVersionUuid), 'multiValued' => false), + $id, + $dom + ) + ); + + $node->appendChild( + $this->createPropertyNode( + $workspaceName, + 'jcr:predecessors', + array('type' => 'reference', 'value' => array(), 'multiValued' => true), + $id, + $dom + ) + ); + + // frozen node for root version + $rootVersionFrozenNodePath = $rootVersionPath . '/jcr:frozenNode'; + $rootVersionFrozenNodeUuid = UUIDHelper::generateUUID(); + $this->addRow( + 'phpcr_nodes', + array( + 'id' => self::$idCounter++, + 'path' => $rootVersionFrozenNodePath, + 'parent' => $rootVersionPath, + 'local_name' => 'jcr:rootVersion', + 'namespace' => '', + 'workspace_name' => $workspaceName, + 'identifier' => $rootVersionFrozenNodeUuid, + 'type' => 'nt:version', + 'props' => '' + . '' + . '' + . '' . $rootVersionFrozenNodeUuid . '' + . '' + . '', + 'depth' => PathHelper::getPathDepth($rootVersionFrozenNodePath), + 'sort_order' => $id - 2, + ) + ); + } } diff --git a/tests/fixtures/system.xml b/tests/fixtures/system.xml new file mode 100644 index 00000000..f37039c5 --- /dev/null +++ b/tests/fixtures/system.xml @@ -0,0 +1,11 @@ + + + + nt:unstructured + + + + nt:unstructured + + +