{"action":"created","comment":{"url":"https://api.github.com/repos/jackalope/jackalope/pulls/comments/30601250","id":30601250,"diff_hunk":"@@ -0,0 +1,224 @@\n+<?php\n+\n+namespace Jackalope\\Version;\n+\n+use Jackalope\\Session;\n+use Jackalope\\Transport\\AddNodeOperation;\n+use Jackalope\\Transport\\WritingInterface;\n+use PHPCR\\InvalidItemStateException;\n+use PHPCR\\NodeInterface;\n+use PHPCR\\PropertyType;\n+use PHPCR\\UnsupportedRepositoryOperationException;\n+use PHPCR\\Util\\UUIDHelper;\n+use PHPCR\\Version\\VersionException;\n+\n+/**\n+ * This class provides a basic implementation of the versioning capabilities as described in the JCR specification. It\n+ * follows the specification, and can be used for any transport layer. However, there might be some performance tweaks\n+ * which can be applied to certain transport layers.\n+ *\n+ * @see http://www.day.com/specs/jcr/2.0/15_Versioning.html\n+ */\n+class VersionHandler\n+{\n+    const MIX_VERSIONABLE = 'mix:versionable';\n+    const MIX_SIMPLE_VERSIONABLE = 'mix:simpleVersionable';\n+\n+    /**\n+     * @var WritingInterface\n+     */\n+    private $objectManager;\n+\n+    /**\n+     * @var Session\n+     */\n+    private $session;\n+\n+    public function __construct(Session $session)\n+    {\n+        $this->objectManager = $session->getObjectManager();\n+        $this->session = $session;\n+    }\n+\n+    /**\n+     * Adds the required version properties and nodes to the given node. Returns an array for the creation of the\n+     * versioning nodes, which will be handled in the NodeProcessor.\n+     * @param NodeInterface $node\n+     * @return array\n+     */\n+    public function addVersionProperties(NodeInterface $node)\n+    {\n+        if ($node->hasProperty('jcr:isCheckedOut')) {\n+            // Versioning properties have already been initialized, nothing to do\n+            return array();\n+        }\n+\n+        $additionalOperations = array();\n+\n+        $session = $node->getSession();\n+\n+        $node->setProperty('jcr:isCheckedOut', true);\n+        if (!$node->hasProperty('jcr:uuid')) {\n+            $node->setProperty('jcr:uuid', UUIDHelper::generateUUID());\n+        }\n+\n+        $versionStorageNode = $session->getNode('/jcr:system/jcr:versionStorage');\n+        $versionHistory = $versionStorageNode->addNode($node->getIdentifier(), 'nt:versionHistory');\n+        $versionHistory->setProperty('jcr:uuid', UUIDHelper::generateUUID());\n+        $versionHistory->setProperty('jcr:versionableUuid', $node->getIdentifier());\n+        $additionalOperations[] = new AddNodeOperation($versionHistory->getPath(), $versionHistory);\n+\n+        // TODO Set jcr:copiedFrom if needed\n+\n+        $versionLabels = $versionHistory->addNode('jcr:versionLabels', 'nt:versionLabels');\n+        $additionalOperations[] = new AddNodeOperation($versionLabels->getPath(), $versionLabels);\n+        $rootVersion = $versionHistory->addNode('jcr:rootVersion', 'nt:version');\n+        $rootVersion->setProperty('jcr:uuid', UUIDHelper::generateUUID());\n+        $rootVersion->setProperty('jcr:predecessors', array());\n+        $rootVersion->setProperty('jcr:successors', array()); // not part of the specification, but seems to be required\n+        $additionalOperations[] = new AddNodeOperation($rootVersion->getPath(), $rootVersion);\n+\n+        // TODO add frozen node to root version\n+\n+        $node->setProperty('jcr:versionHistory', $versionHistory);\n+        $node->setProperty('jcr:baseVersion', $rootVersion);\n+        $node->setProperty('jcr:predecessors', array($rootVersion));\n+\n+        return $additionalOperations;\n+    }\n+\n+    /**\n+     * Performs a checkin for the node on the given path\n+     *\n+     * @param string $path The absolute path of the node to checkin\n+     *\n+     * @return string The path to the node containing the version information\n+     */\n+    public function checkinItem($path)\n+    {\n+        $node = $this->objectManager->getNodeByPath($path);\n+\n+        if (!$node->isNodeType(static::MIX_SIMPLE_VERSIONABLE)) {\n+            throw new UnsupportedRepositoryOperationException(\n+                'Node has to implement at least \"mix:versionable\" to use verisoning operations'\n+            );\n+        }\n+\n+        if ($node->isModified()) {\n+            throw new InvalidItemStateException(\n+                sprintf(\n+                    'Node \"%s\" contains unsaved changes',\n+                    $path\n+                )\n+            );\n+        }\n+\n+        if (!$node->isCheckedOut()) {\n+            return $path;\n+        }\n+\n+        if ($node->hasProperty('jcr:mergeFailed')) {\n+            throw new VersionException(sprintf('Node \"%s\" contains unresolved merge conflicts', $path));\n+        }\n+\n+        // TODO set subgraph to read only\n+\n+        /** @var NodeInterface $versionHistoryNode */\n+        $versionHistoryNode = $node->getPropertyValue('jcr:versionHistory');\n+\n+        // FIXME add some kind of sharding\n+        /** @var NodeInterface $versionNode */\n+        $versionNode = $versionHistoryNode->addNode(UUIDHelper::generateUUID(), 'nt:version');\n+        $versionNode->setProperty('jcr:uuid', UUIDHelper::generateUUID());\n+        $versionNode->setProperty('jcr:created', new \\DateTime());\n+        $versionNode->setProperty('jcr:successors', array());\n+\n+        // TODO create frozen node\n+        $frozenNode = $versionNode->addNode('jcr:frozenNode', 'nt:frozenNode');\n+        $frozenNode->setProperty('jcr:frozenPrimaryType', $node->getProperty('jcr:primaryType'));\n+        if ($frozenNode->hasProperty('jcr:mixinTypes')) {\n+            $frozenNode->setProperty('jcr:frozenMixinTypes', $node->getProperty('jcr:mixinTypes'));\n+        }\n+        $frozenNode->setProperty('jcr:frozenUuid', $node->getProperty('jcr:uuid'));\n+\n+        /** @var NodeInterface $baseVersionNode */\n+        $baseVersionNode = $node->getPropertyValue('jcr:baseVersion');\n+        $baseVersionNode->setProperty('jcr:successors', array($versionNode), PropertyType::REFERENCE, false);\n+        $baseVersionNode->setModified();","path":"src/Jackalope/Version/VersionHandler.php","position":147,"original_position":147,"commit_id":"b1013bdfabad27b2e51d1e01182c4cbd9f9695c2","original_commit_id":"b1013bdfabad27b2e51d1e01182c4cbd9f9695c2","user":{"login":"danrot","id":405874,"avatar_url":"https://avatars.githubusercontent.com/u/405874?v=3","gravatar_id":"","url":"https://api.github.com/users/danrot","html_url":"https://github.com/danrot","followers_url":"https://api.github.com/users/danrot/followers","following_url":"https://api.github.com/users/danrot/following{/other_user}","gists_url":"https://api.github.com/users/danrot/gists{/gist_id}","starred_url":"https://api.github.com/users/danrot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danrot/subscriptions","organizations_url":"https://api.github.com/users/danrot/orgs","repos_url":"https://api.github.com/users/danrot/repos","events_url":"https://api.github.com/users/danrot/events{/privacy}","received_events_url":"https://api.github.com/users/danrot/received_events","type":"User","site_admin":false},"body":"I find myself doing this quite often, since `setProperty` doesn't handle the state at all, if it is called with `$validate = false`. I am not sure if this is ok this way, or we should change the behavior of the `setProperty`-method resp. the subsequent function calls. /cc @dbu @lsmith77 @dantleech ","created_at":"2015-05-19T14:00:11Z","updated_at":"2015-05-19T14:00:11Z","html_url":"https://github.com/jackalope/jackalope/pull/286#discussion_r30601250","pull_request_url":"https://api.github.com/repos/jackalope/jackalope/pulls/286","_links":{"self":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/comments/30601250"},"html":{"href":"https://github.com/jackalope/jackalope/pull/286#discussion_r30601250"},"pull_request":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/286"}}},"pull_request":{"url":"https://api.github.com/repos/jackalope/jackalope/pulls/286","id":35732631,"html_url":"https://github.com/jackalope/jackalope/pull/286","diff_url":"https://github.com/jackalope/jackalope/pull/286.diff","patch_url":"https://github.com/jackalope/jackalope/pull/286.patch","issue_url":"https://api.github.com/repos/jackalope/jackalope/issues/286","number":286,"state":"open","locked":false,"title":"[WIP] Adding version history support","user":{"login":"danrot","id":405874,"avatar_url":"https://avatars.githubusercontent.com/u/405874?v=3","gravatar_id":"","url":"https://api.github.com/users/danrot","html_url":"https://github.com/danrot","followers_url":"https://api.github.com/users/danrot/followers","following_url":"https://api.github.com/users/danrot/following{/other_user}","gists_url":"https://api.github.com/users/danrot/gists{/gist_id}","starred_url":"https://api.github.com/users/danrot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danrot/subscriptions","organizations_url":"https://api.github.com/users/danrot/orgs","repos_url":"https://api.github.com/users/danrot/repos","events_url":"https://api.github.com/users/danrot/events{/privacy}","received_events_url":"https://api.github.com/users/danrot/received_events","type":"User","site_admin":false},"body":"These changes will make the tests in the `VersionHistoryTest` working. It adds correct chaining of all the versions.\r\n\r\nThis PR is based on the work in #285","created_at":"2015-05-19T13:57:09Z","updated_at":"2015-05-19T14:00:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"30af05b6641666f3b6b59139d77274bb86fa03d9","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/jackalope/jackalope/pulls/286/commits","review_comments_url":"https://api.github.com/repos/jackalope/jackalope/pulls/286/comments","review_comment_url":"https://api.github.com/repos/jackalope/jackalope/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jackalope/jackalope/issues/286/comments","statuses_url":"https://api.github.com/repos/jackalope/jackalope/statuses/b1013bdfabad27b2e51d1e01182c4cbd9f9695c2","head":{"label":"danrot:version-history","ref":"version-history","sha":"b1013bdfabad27b2e51d1e01182c4cbd9f9695c2","user":{"login":"danrot","id":405874,"avatar_url":"https://avatars.githubusercontent.com/u/405874?v=3","gravatar_id":"","url":"https://api.github.com/users/danrot","html_url":"https://github.com/danrot","followers_url":"https://api.github.com/users/danrot/followers","following_url":"https://api.github.com/users/danrot/following{/other_user}","gists_url":"https://api.github.com/users/danrot/gists{/gist_id}","starred_url":"https://api.github.com/users/danrot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danrot/subscriptions","organizations_url":"https://api.github.com/users/danrot/orgs","repos_url":"https://api.github.com/users/danrot/repos","events_url":"https://api.github.com/users/danrot/events{/privacy}","received_events_url":"https://api.github.com/users/danrot/received_events","type":"User","site_admin":false},"repo":{"id":30755794,"name":"jackalope","full_name":"danrot/jackalope","owner":{"login":"danrot","id":405874,"avatar_url":"https://avatars.githubusercontent.com/u/405874?v=3","gravatar_id":"","url":"https://api.github.com/users/danrot","html_url":"https://github.com/danrot","followers_url":"https://api.github.com/users/danrot/followers","following_url":"https://api.github.com/users/danrot/following{/other_user}","gists_url":"https://api.github.com/users/danrot/gists{/gist_id}","starred_url":"https://api.github.com/users/danrot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danrot/subscriptions","organizations_url":"https://api.github.com/users/danrot/orgs","repos_url":"https://api.github.com/users/danrot/repos","events_url":"https://api.github.com/users/danrot/events{/privacy}","received_events_url":"https://api.github.com/users/danrot/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/danrot/jackalope","description":"Jackalope implements the PHPCR API. This modular library contains bindings to store data with the Doctrine DBAL or in the Jackrabbit JCR server part","fork":true,"url":"https://api.github.com/repos/danrot/jackalope","forks_url":"https://api.github.com/repos/danrot/jackalope/forks","keys_url":"https://api.github.com/repos/danrot/jackalope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/danrot/jackalope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/danrot/jackalope/teams","hooks_url":"https://api.github.com/repos/danrot/jackalope/hooks","issue_events_url":"https://api.github.com/repos/danrot/jackalope/issues/events{/number}","events_url":"https://api.github.com/repos/danrot/jackalope/events","assignees_url":"https://api.github.com/repos/danrot/jackalope/assignees{/user}","branches_url":"https://api.github.com/repos/danrot/jackalope/branches{/branch}","tags_url":"https://api.github.com/repos/danrot/jackalope/tags","blobs_url":"https://api.github.com/repos/danrot/jackalope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/danrot/jackalope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/danrot/jackalope/git/refs{/sha}","trees_url":"https://api.github.com/repos/danrot/jackalope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/danrot/jackalope/statuses/{sha}","languages_url":"https://api.github.com/repos/danrot/jackalope/languages","stargazers_url":"https://api.github.com/repos/danrot/jackalope/stargazers","contributors_url":"https://api.github.com/repos/danrot/jackalope/contributors","subscribers_url":"https://api.github.com/repos/danrot/jackalope/subscribers","subscription_url":"https://api.github.com/repos/danrot/jackalope/subscription","commits_url":"https://api.github.com/repos/danrot/jackalope/commits{/sha}","git_commits_url":"https://api.github.com/repos/danrot/jackalope/git/commits{/sha}","comments_url":"https://api.github.com/repos/danrot/jackalope/comments{/number}","issue_comment_url":"https://api.github.com/repos/danrot/jackalope/issues/comments{/number}","contents_url":"https://api.github.com/repos/danrot/jackalope/contents/{+path}","compare_url":"https://api.github.com/repos/danrot/jackalope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/danrot/jackalope/merges","archive_url":"https://api.github.com/repos/danrot/jackalope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/danrot/jackalope/downloads","issues_url":"https://api.github.com/repos/danrot/jackalope/issues{/number}","pulls_url":"https://api.github.com/repos/danrot/jackalope/pulls{/number}","milestones_url":"https://api.github.com/repos/danrot/jackalope/milestones{/number}","notifications_url":"https://api.github.com/repos/danrot/jackalope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/danrot/jackalope/labels{/name}","releases_url":"https://api.github.com/repos/danrot/jackalope/releases{/id}","created_at":"2015-02-13T12:38:14Z","updated_at":"2015-02-13T12:38:15Z","pushed_at":"2015-05-19T13:22:05Z","git_url":"git://github.com/danrot/jackalope.git","ssh_url":"git@github.com:danrot/jackalope.git","clone_url":"https://github.com/danrot/jackalope.git","svn_url":"https://github.com/danrot/jackalope","homepage":"http://jackalope.github.io","size":9723,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"jackalope:versioning","ref":"versioning","sha":"1506d9fc88b2d1024fbbfb6b5a7fe4db766bf1da","user":{"login":"jackalope","id":70245,"avatar_url":"https://avatars.githubusercontent.com/u/70245?v=3","gravatar_id":"","url":"https://api.github.com/users/jackalope","html_url":"https://github.com/jackalope","followers_url":"https://api.github.com/users/jackalope/followers","following_url":"https://api.github.com/users/jackalope/following{/other_user}","gists_url":"https://api.github.com/users/jackalope/gists{/gist_id}","starred_url":"https://api.github.com/users/jackalope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jackalope/subscriptions","organizations_url":"https://api.github.com/users/jackalope/orgs","repos_url":"https://api.github.com/users/jackalope/repos","events_url":"https://api.github.com/users/jackalope/events{/privacy}","received_events_url":"https://api.github.com/users/jackalope/received_events","type":"Organization","site_admin":false},"repo":{"id":563272,"name":"jackalope","full_name":"jackalope/jackalope","owner":{"login":"jackalope","id":70245,"avatar_url":"https://avatars.githubusercontent.com/u/70245?v=3","gravatar_id":"","url":"https://api.github.com/users/jackalope","html_url":"https://github.com/jackalope","followers_url":"https://api.github.com/users/jackalope/followers","following_url":"https://api.github.com/users/jackalope/following{/other_user}","gists_url":"https://api.github.com/users/jackalope/gists{/gist_id}","starred_url":"https://api.github.com/users/jackalope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jackalope/subscriptions","organizations_url":"https://api.github.com/users/jackalope/orgs","repos_url":"https://api.github.com/users/jackalope/repos","events_url":"https://api.github.com/users/jackalope/events{/privacy}","received_events_url":"https://api.github.com/users/jackalope/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/jackalope/jackalope","description":"Jackalope implements the PHPCR API. This modular library contains bindings to store data with the Doctrine DBAL or in the Jackrabbit JCR server part","fork":false,"url":"https://api.github.com/repos/jackalope/jackalope","forks_url":"https://api.github.com/repos/jackalope/jackalope/forks","keys_url":"https://api.github.com/repos/jackalope/jackalope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jackalope/jackalope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jackalope/jackalope/teams","hooks_url":"https://api.github.com/repos/jackalope/jackalope/hooks","issue_events_url":"https://api.github.com/repos/jackalope/jackalope/issues/events{/number}","events_url":"https://api.github.com/repos/jackalope/jackalope/events","assignees_url":"https://api.github.com/repos/jackalope/jackalope/assignees{/user}","branches_url":"https://api.github.com/repos/jackalope/jackalope/branches{/branch}","tags_url":"https://api.github.com/repos/jackalope/jackalope/tags","blobs_url":"https://api.github.com/repos/jackalope/jackalope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jackalope/jackalope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jackalope/jackalope/git/refs{/sha}","trees_url":"https://api.github.com/repos/jackalope/jackalope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jackalope/jackalope/statuses/{sha}","languages_url":"https://api.github.com/repos/jackalope/jackalope/languages","stargazers_url":"https://api.github.com/repos/jackalope/jackalope/stargazers","contributors_url":"https://api.github.com/repos/jackalope/jackalope/contributors","subscribers_url":"https://api.github.com/repos/jackalope/jackalope/subscribers","subscription_url":"https://api.github.com/repos/jackalope/jackalope/subscription","commits_url":"https://api.github.com/repos/jackalope/jackalope/commits{/sha}","git_commits_url":"https://api.github.com/repos/jackalope/jackalope/git/commits{/sha}","comments_url":"https://api.github.com/repos/jackalope/jackalope/comments{/number}","issue_comment_url":"https://api.github.com/repos/jackalope/jackalope/issues/comments{/number}","contents_url":"https://api.github.com/repos/jackalope/jackalope/contents/{+path}","compare_url":"https://api.github.com/repos/jackalope/jackalope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jackalope/jackalope/merges","archive_url":"https://api.github.com/repos/jackalope/jackalope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jackalope/jackalope/downloads","issues_url":"https://api.github.com/repos/jackalope/jackalope/issues{/number}","pulls_url":"https://api.github.com/repos/jackalope/jackalope/pulls{/number}","milestones_url":"https://api.github.com/repos/jackalope/jackalope/milestones{/number}","notifications_url":"https://api.github.com/repos/jackalope/jackalope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jackalope/jackalope/labels{/name}","releases_url":"https://api.github.com/repos/jackalope/jackalope/releases{/id}","created_at":"2010-03-15T13:51:08Z","updated_at":"2015-05-04T13:28:23Z","pushed_at":"2015-05-19T13:57:10Z","git_url":"git://github.com/jackalope/jackalope.git","ssh_url":"git@github.com:jackalope/jackalope.git","clone_url":"https://github.com/jackalope/jackalope.git","svn_url":"https://github.com/jackalope/jackalope","homepage":"http://jackalope.github.io","size":11710,"stargazers_count":177,"watchers_count":177,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":57,"mirror_url":null,"open_issues_count":43,"forks":57,"open_issues":43,"watchers":177,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/286"},"html":{"href":"https://github.com/jackalope/jackalope/pull/286"},"issue":{"href":"https://api.github.com/repos/jackalope/jackalope/issues/286"},"comments":{"href":"https://api.github.com/repos/jackalope/jackalope/issues/286/comments"},"review_comments":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/286/comments"},"review_comment":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jackalope/jackalope/pulls/286/commits"},"statuses":{"href":"https://api.github.com/repos/jackalope/jackalope/statuses/b1013bdfabad27b2e51d1e01182c4cbd9f9695c2"}}}}