diff --git a/.travis.yml b/.travis.yml index 6241d64..558fd29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ php: before_script: - composer selfupdate - composer install - - composer require "jackalope/jackalope" "dev-node_validator as dev-master" script: phpunit diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b0fc6a3..452b6d8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,7 @@ - + diff --git a/src/Transport/Fs/Client.php b/src/Transport/Fs/Client.php index f833900..581faad 100644 --- a/src/Transport/Fs/Client.php +++ b/src/Transport/Fs/Client.php @@ -38,10 +38,12 @@ use PHPCR\PathNotFoundException; use PHPCR\ReferentialIntegrityException; use Jackalope\Transport\Fs\Model\Node; +use Jackalope\Transport\NodeTypeManagementInterface; +use Jackalope\Transport\Fs\NodeType\NodeTypeStorage; /** */ -class Client extends BaseTransport implements WorkspaceManagementInterface, WritingInterface, QueryInterface +class Client extends BaseTransport implements WorkspaceManagementInterface, WritingInterface, QueryInterface, NodeTypeManagementInterface { private $loggedIn; @@ -83,6 +85,7 @@ public function __construct($factory, $parameters = array(), Filesystem $filesys $this->storage = new Storage(new Filesystem($adapter), $this->eventDispatcher); $this->valueConverter = new ValueConverter(); $this->nodeSerializer = new YamlNodeSerializer(); + $this->nodeTypeStorage = new NodeTypeStorage($this->storage); $this->factory = $factory; $this->registerEventSubscribers(); @@ -158,6 +161,29 @@ public function getRepositoryDescriptors() ); } + public function registerNodeTypes($nodeTypes, $allowUpdate) + { + $standardNodeTypes = StandardNodeTypes::getNodeTypeData(); + + foreach ($nodeTypes as $nodeType) { + if (isset($standardNodeTypes[$nodeType->getName()])) { + throw new RepositoryException(sprintf( + 'Cannot overwrite standard node type "%s"', $nodeType->getName() + )); + } + + if (!$allowUpdate) { + if ($this->nodeTypeStorage->hasNodeType($nodeType->getName())) { + throw new RepositoryException(sprintf( + 'Node type "%s" already exists and allowUpdate is false', + $nodeType->getName() + )); + } + } + $this->nodeTypeStorage->registerNodeType($this->workspaceName, $nodeType); + } + } + /** * {@inheritDoc} */ @@ -348,8 +374,12 @@ public function setNodeTypeManager($nodeTypeManager) */ public function getNodeTypes($nodeTypes = array()) { - $standardTypes = StandardNodeTypes::getNodeTypeData(); - return $standardTypes; + $types = array_merge( + StandardNodeTypes::getNodeTypeData(), + $this->nodeTypeStorage->getNodeTypes($this->workspaceName) + ); + + return $types; } /** @@ -587,6 +617,8 @@ public function updateProperties(JackalopeNode $phpcrNode) public function registerNamespace($prefix, $uri) { $this->storage->registerNamespace($this->workspaceName, $prefix, $uri); + + $this->init(); } /** diff --git a/src/Transport/Fs/Filesystem/Storage.php b/src/Transport/Fs/Filesystem/Storage.php index 2b490ed..a7409e4 100644 --- a/src/Transport/Fs/Filesystem/Storage.php +++ b/src/Transport/Fs/Filesystem/Storage.php @@ -149,6 +149,12 @@ public function workspaceInit($name) $node = new Node(); $node->setProperty('jcr:primaryType', 'nt:unstructured', 'Name'); $this->writeNode($name, '/', $node); + $node = new Node(); + $node->setProperty('jcr:primaryType', 'rep:system', 'Name'); + $this->writeNode($name, '/jcr:system', $node); + $node = new Node(); + $node->setProperty('jcr:primaryType', 'rep:nodeTypes', 'Name'); + $this->writeNode($name, '/jcr:system/jcr:nodeTypes', $node); } public function ls($workspace, $path) diff --git a/src/Transport/Fs/NodeType/NodeTypeStorage.php b/src/Transport/Fs/NodeType/NodeTypeStorage.php new file mode 100644 index 0000000..2bccafb --- /dev/null +++ b/src/Transport/Fs/NodeType/NodeTypeStorage.php @@ -0,0 +1,185 @@ +storage = $storage; + } + + public function hasNodeType($workspace, $nodeType) + { + return $this->storage->nodeExists($workspace, self::PATH . '/' . $nodeType); + } + + public function registerNodeType($workspace, NodeTypeDefinitionInterface $definition) + { + $node = new Node(); + $node->setProperty('jcr:hasOrderableChildNodes', $definition->hasOrderableChildNodes(), 'Boolean'); + $node->setProperty('jcr:isMixin', $definition->isMixin()); + $node->setProperty('jcr:nodeTypeName', $definition->getName(), 'Name'); + $node->setProperty('jcr:superTypes', $definition->getDeclaredSupertypeNames(), 'Name'); + $node->setProperty('jcr:primaryType', 'nt:nodeType', 'Name'); + + $this->storage->writeNode($workspace, self::PATH . '/' . $definition->getName(), $node); + + foreach ($definition->getDeclaredPropertyDefinitions() ? : array() as $propDefinition) { + $propNode = new Node(); + $propNode->setProperty('jcr:requiredType', $propDefinition->getRequiredType(), 'Reference'); + $propNode->setProperty('jcr:autoCreated', $propDefinition->isAutoCreated(), 'Boolean'); + $propNode->setProperty('jcr:mandatory', $propDefinition->isMandatory(), 'Boolean'); + $propNode->setProperty('jcr:protected', $propDefinition->isProtected(), 'Boolean'); + $propNode->setProperty('jcr:onParentVersion', $propDefinition->getOnParentVersion(), 'String'); + $propNode->setProperty('jcr:name', $propDefinition->getName(), 'String'); + $propNode->setProperty('jcr:multiple', $propDefinition->isMultiple(), 'Boolean'); + $propNode->setProperty('jcr:primaryType', 'nt:propertyDefinition', 'Name'); + + $this->storage->writeNode( + $workspace, + self::PATH . '/' . $definition->getName() . '/prop-' . $propDefinition->getName(), + $propNode + ); + } + + foreach ($definition->getDeclaredChildNodeDefinitions() ? : array() as $childDefinition) { + $childNode = new Node(); + $childNode->setProperty('jcr:name', $childDefinition->getName(), 'String'); + $childNode->setProperty('jcr:requiredPrimaryTypes', $childDefinition->getRequiredPrimaryTypeNames(), 'Reference'); + $childNode->setProperty('jcr:autoCreated', $childDefinition->isAutoCreated(), 'Boolean'); + $childNode->setProperty('jcr:defaultPrimaryType', $childDefinition->getDefaultPrimaryTypeName(), 'Name'); + $childNode->setProperty('jcr:protected', $childDefinition->isProtected(), 'Boolean'); + $childNode->setProperty('jcr:mandatory', $childDefinition->isMandatory(), 'Boolean'); + $childNode->setProperty('jcr:sameNameSiblings', $childDefinition->allowsSameNameSiblings()); + $childNode->setProperty('jcr:onParentVersion', $childDefinition->getOnParentVersion(), 'String'); + $childNode->setProperty('jcr:primaryType', 'nt:childNodeDefinition', 'Name'); + + $this->storage->writeNode( + $workspace, + self::PATH . '/' . $definition->getName() . '/child-' . $childDefinition->getName(), + $childNode + ); + } + } + + public function getNodeTypes($workspace) + { + $nodeTypeNames = $this->storage->ls($workspace, self::PATH); + $nodeTypeData = array(); + + foreach ($nodeTypeNames['dirs'] as $nodeTypeName) { + $nodeTypePath = self::PATH . '/' . $nodeTypeName; + $node = $this->storage->readNode($workspace, $nodeTypePath); + + $propertyData = $this->getPropertyData($workspace, $nodeTypeName, $nodeTypePath); + $childData = $this->getChildData($workspace, $nodeTypeName, $nodeTypePath); + + $nodeTypeData[$nodeTypeName] = array( + 'name' => $nodeTypeName, + 'isAbstract' => false, + 'isMixin' => $node->getPropertyValue('jcr:isMixin'), + 'isQueryable' => true, + 'hasOrderableChildNodes' => $node->getPropertyValue('jcr:hasOrderableChildNodes'), + 'primaryItemName' => null, + 'declaredSuperTypeNames' => $node->getPropertyValue('jcr:superTypes'), + 'declaredPropertyDefinitions' => $propertyData, + 'declaredNodeDefinitions' => $childData, + ); + } + + return $nodeTypeData; + } + + private function getPropertyData($workspace, $nodeType, $path) + { + $propertyNodeNames = $this->storage->ls($workspace, $path); + + $propertyData = array(); + + foreach ($propertyNodeNames['dirs'] as $propertyNodeName) { + if (substr($propertyNodeName, 0, 4) !== 'prop') { + continue; + } + $node = $this->storage->readNode($workspace, $path . '/' . $propertyNodeName); + + $data = array( + 'declaringNodeType' => $nodeType, + 'name' => $node->getPropertyValue('jcr:name'), + 'isAutoCreated' => $node->getPropertyValue('jcr:autoCreated'), + 'isMandatory' => $node->getPropertyValue('jcr:mandatory'), + 'isProtected' => $node->getPropertyValue('jcr:protected'), + 'onParentVersion' => $node->getPropertyValue('jcr:onParentVersion'), + 'requiredType' => $node->getPropertyValue('jcr:requiredType'), + 'multiple' => $node->getPropertyValue('jcr:multiple'), + 'fullTextSearchable' => true, + 'queryOrderable' => true, + 'availableQueryOperators' => + array( + 0 => 'jcr.operator.equal.to', + 1 => 'jcr.operator.not.equal.to', + 2 => 'jcr.operator.greater.than', + 3 => 'jcr.operator.greater.than.or.equal.to', + 4 => 'jcr.operator.less.than', + 5 => 'jcr.operator.less.than.or.equal.to', + 6 => 'jcr.operator.like', + ), + ); + + $propertyData[] = $data; + } + + return $propertyData; + } + + private function getChildData($workspace, $nodeType, $path) + { + $childNodeNames = $this->storage->ls($workspace, $path); + + $childData = array(); + + foreach ($childNodeNames['dirs'] as $childNodeName) { + if (substr($childNodeName, 0, 5) !== 'child') { + continue; + } + + $node = $this->storage->readNode($workspace, $path . '/' . $childNodeName); + + $data = array( + 'declaringNodeType' => $nodeType, + ); + + $map = array( + 'name' => 'jcr:name', + 'isAutoCreated' => 'jcr:autoCreated', + 'isMandatory' => 'jcr:mandatory', + 'isProtected' => 'jcr:protected', + 'onParentVersion' => 'jcr:onParentVersion', + 'allowsSameNameSiblings' => 'jcr:sameNameSiblings', + 'defaultPrimaryTypeName' => 'jcr:defaultPrimaryType', + 'requiredPrimaryTypeNames' => 'jcr:requiredPrimaryTypes', + ); + + $this->mapData($map, $node, $data); + + $childData[] = $data; + } + + return $childData; + } + + private function mapData($map, $node, &$data) + { + foreach ($map as $key => $propertyName) { + $data[$key] = $node->hasProperty($propertyName) ? $node->getPropertyValue($propertyName) : null; + } + } +} diff --git a/tests/ImplementationLoader.php b/tests/ImplementationLoader.php index 9a76a40..a6f5ce9 100644 --- a/tests/ImplementationLoader.php +++ b/tests/ImplementationLoader.php @@ -36,7 +36,6 @@ protected function __construct() 'AccessControlManagement', 'Locking', 'LifecycleManagement', - 'NodeTypeManagement', 'RetentionAndHold', 'Transactions', 'SameNameSiblings',