/** * Create a property, either from server data or locally * * To indicate a property has newly been created locally, make sure to pass * true for the $new parameter. * * @param object $factory an object factory implementing "get" as described in \jackalope\Factory * @param array $data array with fields * type (integer or string from PropertyType) * and value (data for creating value object - array for multivalue property) * @param string $path the absolute path of this item * @param Session the session instance * @param ObjectManager the objectmanager instance - the caller has to take care of registering this item with the object manager * @param boolean $new optional: set to true to make this property aware its not yet existing on the server. defaults to false */ public function __construct($factory, array $data, $path, Session $session, ObjectManager $objectManager, $new = false) { parent::__construct($factory, $path, $session, $objectManager, $new); $type = $data['type']; if (is_string($type)) { $type = \PHPCR\PropertyType::valueFromName($type); } elseif (!is_numeric($type)) { throw new \PHPCR\RepositoryException("INTERNAL ERROR -- No valid type specified ({$type})"); } else { //sanity check. this will throw InvalidArgumentException if $type is not a valid type \PHPCR\PropertyType::nameFromValue($type); } $this->type = $type; if (is_array($data['value'])) { $this->isMultiple = true; $this->value = array(); foreach ($data['value'] as $value) { $this->value[] = Helper::convertType($value, $type); } } elseif (null !== $data['value']) { $this->value = Helper::convertType($data['value'], $type); } else { throw new \PHPCR\RepositoryException('INTERNAL ERROR -- data[value] may not be null'); } }
public function setProperty($name, $value = null, $type = 'String') { if (is_integer($type)) { $type = PropertyType::nameFromValue($type); } $this->properties[$name] = array('value' => $value, 'type' => $type); }
/** * Determine PropertyType from on variable type. * * This is most of the remainder of ValueFactory that is still needed. * * - if the given $value is a Node object, type will be REFERENCE, unless * $weak is set to true which results in WEAKREFERENCE * - if the given $value is a DateTime object, the type will be DATE. * - if the $value is an empty array, the type is arbitrarily set to STRING * - if the $value is a non-empty array, the type of its first element is * chosen. * * Note that string is converted to date exactly if it matches the jcr * formatting spec for dates (sYYYY-MM-DDThh:mm:ss.sssTZD) according to * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.6.4.3%20From%20DATE%20To * * @param mixed $value The variable we need to know the type of * @param boolean $weak When a Node is given as $value this can be given * as true to create a WEAKREFERENCE. * * @return int One of the type constants * * @throws ValueFormatException if the type can not be determined */ public function determineType($value, $weak = false) { if (is_array($value)) { if (0 === count($value)) { // there is no value to determine the type on. we arbitrarily // chose string, which is what jackrabbit does as well. return PropertyType::STRING; } $value = reset($value); } return PropertyType::determineType($value, $weak); }
/** * Convert a node type into YAML format * * @param NodeTypeInterface * * @return string */ public function serialize(NodeTypeInterface $nt) { $out = array('name' => $nt->getName(), 'declared_supertypes' => $nt->getDeclaredSupertypeNames(), 'abstract' => (bool) $nt->isAbstract(), 'mixin' => (bool) $nt->isMixin(), 'orderable_child_nodes' => (bool) $nt->hasOrderableChildNodes(), 'queryable' => (bool) $nt->isQueryable(), 'primary_item' => $nt->getPrimaryItemName(), 'properties' => array(), 'children' => array()); foreach ($nt->getDeclaredPropertyDefinitions() as $pd) { $property = $this->getItemDefinitionArray($pd); $property = array_merge($property, array('required_type' => PropertyType::nameFromValue($pd->getRequiredType()), 'value_constraints' => $pd->getValueConstraints(), 'default_values' => $pd->getDefaultValues(), 'multiple' => (bool) $pd->isMultiple(), 'available_query_operators' => $pd->getAvailableQueryOperators(), 'full_text_searchable' => (bool) $pd->isFullTextSearchable(), 'query_orderable' => (bool) $pd->isQueryOrderable())); $out['properties'][] = $property; } foreach ($nt->getDeclaredChildNodeDefinitions() as $cd) { $child = $this->getItemDefinitionArray($cd); $child = array_merge($child, array('required_primary_types' => $cd->getRequiredPrimaryTypeNames(), 'default_primary_type' => $cd->getDefaultPrimaryTypeName(), 'same_name_siblings' => (bool) $cd->allowsSameNameSiblings())); $out['children'][] = $child; } return Yaml::dump($out, 10); }
private function walkNode(NodeBuilder $builder, NodeInterface $parentNode) { for ($i = $builder->getRangeStart(); $i <= $builder->getRangeEnd(); $i++) { $name = str_replace(NodeBuilder::ITERATION_TOKEN, $i, $builder->getName()); $this->count++; $node = $parentNode->addNode($name, $builder->getNodeTypeName()); foreach ($builder->getProperties() as $property) { list($name, $value, $type) = $property; $node->setProperty($name, $value, PropertyType::valueFromName($type)); } foreach ($builder->getNodes() as $childNodeBuilder) { $this->walkNode($childNodeBuilder, $node); } } }
private function configure() { $this->nodeTypeMap = array('name' => function ($t, $v) { $t->setName($v); }, 'auto_created' => function ($t, $v) { $t->setAutoCreated((bool) $v); }, 'declared_supertypes' => function ($t, $v) { $t->setDeclaredSuperTypeNames((array) $v); }, 'abstract' => function ($t, $v) { $t->setAbstract($v); }, 'mixin' => function ($t, $v) { $t->setMixin((bool) $v); }, 'orderable_child_nodes' => function ($t, $v) { $t->setOrderableChildNodes((bool) $v); }, 'primary_item' => function ($t, $v) { $t->setPrimaryItemName((string) $v); }, 'queryable' => function ($t, $v) { $t->setQueryable((bool) $v); }); $this->childDefinitionMap = array_merge($this->getItemDefinitionMap(), array('default_primary_type' => function ($t, $v) { $t->setDefaultPrimaryTypeName($v); }, 'same_name_siblings' => function ($t, $v) { $t->setSameNameSiblings((bool) $v); }, 'required_primary_types' => function ($t, $v) { $t->setRequiredPrimaryTypeNames((array) $v); })); $this->propertyMap = array_merge($this->getItemDefinitionMap(), array('multiple' => function ($t, $v) { $t->setMultiple((bool) $v); }, 'default_values' => function ($t, $v) { $t->setDefaultValues((array) $v); }, 'full_text_searchable' => function ($t, $v) { $t->setFullTextSearchable((bool) $v); }, 'query_orderable' => function ($t, $v) { $t->setQueryOrderable((bool) $v); }, 'value_constraints' => function ($t, $v) { $t->setValueConstraints((array) $v); }, 'required_type' => function ($t, $v) { $t->setRequiredType(PropertyType::valueFromName($v)); }, 'available_query_operators' => function ($t, $v) { $queryOperators = array(); foreach ($v as $queryOperator) { $queryOperator = constant('PHPCR\\Query\\QOM\\QueryObjectModelConstantsInterface::' . strtoupper(str_replace('.', '_', $queryOperator))); $queryOperators[] = $queryOperator; } $t->setAvailableQueryOperators($queryOperators); })); }
protected function fromXML(DOMElement $node) { parent::fromXML($node); $this->requiredType = \PHPCR\PropertyType::valueFromName($node->getAttribute('requiredType')); $this->isMultiple = Helper::getBoolAttribute($node, 'multiple'); $this->isFullTextSearchable = Helper::getBoolAttribute($node, 'fullTextSearchable'); $this->isQueryOrderable = Helper::getBoolAttribute($node, 'queryOrderable'); $xp = new DOMXPath($node->ownerDocument); $valueConstraints = $xp->query('valueConstraints/valueConstraint', $node); foreach ($valueConstraints as $valueConstraint) { $this->valueConstraints[] = $valueConstraint->nodeValue; } $availableQueryOperators = $xp->query('availableQueryOperators/availableQueryOperator', $node); foreach ($availableQueryOperators as $availableQueryOperator) { $this->availableQueryOperators[] = $availableQueryOperator->nodeValue; } $defaultValues = $xp->query('defaultValues/defaultValue', $node); foreach ($defaultValues as $defaultValue) { $this->defaultValues[] = $defaultValue->nodeValue; } }
public function execute(InputInterface $input, OutputInterface $output) { $session = $this->get('phpcr.session'); $pathHelper = $this->get('helper.path'); $path = $session->getAbsPath($input->getArgument('path')); $value = $input->getArgument('value'); $type = $input->getOption('type'); $nodePath = $pathHelper->getParentPath($path); $propName = $pathHelper->getNodeName($path); $nodes = $session->findNodes($nodePath); foreach ($nodes as $node) { $intType = null; if ($type) { $intType = PropertyType::valueFromName($type); if ($intType === PropertyType::REFERENCE || $intType === PropertyType::WEAKREFERENCE) { // convert path to UUID if (false === UUIDHelper::isUuid($value)) { $path = $value; try { $targetNode = $session->getNode($path); $value = $targetNode->getIdentifier(); } catch (PathNotFoundException $e) { } if (null === $value) { throw new \InvalidArgumentException(sprintf('Node at path "%s" specified for reference is not referenceable', $path)); } } } } else { try { $property = $node->getProperty($propName); $intType = $property->getType(); } catch (PathNotFoundException $e) { // property doesn't exist and no type specified, default to string $intType = PropertyType::STRING; } } $node->setProperty($propName, $value, $intType); } }
/** * Convert property definition xml into array. * * @param \DOMElement $node * @return array */ public function getPropertyDefinitionFromXml(DOMElement $node) { $data = $this->getItemDefinitionFromXml($node); $data['requiredType'] = \PHPCR\PropertyType::valueFromName($node->getAttribute('requiredType')); $data['multiple'] = Helper::getBoolAttribute($node, 'multiple'); $data['fullTextSearchable'] = Helper::getBoolAttribute($node, 'fullTextSearchable'); $data['queryOrderable'] = Helper::getBoolAttribute($node, 'queryOrderable'); $xp = new DOMXPath($node->ownerDocument); $valueConstraints = $xp->query('valueConstraints/valueConstraint', $node); foreach ($valueConstraints as $valueConstraint) { $data['valueConstraints'][] = $valueConstraint->nodeValue; } $availableQueryOperators = $xp->query('availableQueryOperators/availableQueryOperator', $node); foreach ($availableQueryOperators as $availableQueryOperator) { $data['availableQueryOperators'][] = $availableQueryOperator->nodeValue; } $defaultValues = $xp->query('defaultValues/defaultValue', $node); foreach ($defaultValues as $defaultValue) { $data['defaultValues'][] = $defaultValue->nodeValue; } return $data; }
/** * Initialize or update this object with raw data from backend. * * @param array $rawData in the format as returned from Jackalope\Transport\TransportInterface * @param boolean $update whether to initialize this object or update * @param boolean $keepChanges only used if $update is true, same as $keepChanges in refresh() * * @see Node::__construct() * @see Node::refresh() */ private function parseData($rawData, $update, $keepChanges = false) { //TODO: refactor to use hash array instead of stdClass struct if ($update) { // keep backup of old state so we can remove what needs to be removed $oldNodes = array_flip(array_values($this->nodes)); $oldProperties = $this->properties; } /* * we collect all nodes coming from the backend. if we update with * $keepChanges, we use this to update the node list rather than losing * reorders * * properties are easy as they are not ordered. */ $nodesInBackend = array(); foreach ($rawData as $key => $value) { $node = false; // reset to avoid trouble if (is_object($value)) { // this is a node. add it if if (!$update || !$keepChanges || isset($oldNodes[$key]) || !($node = $this->objectManager->getCachedNode($this->path . '/' . $key))) { // for all those cases, if the node was moved away or is deleted in current session, we do not add it if (!$this->objectManager->isNodeMoved($this->path . '/' . $key) && !$this->objectManager->isNodeDeleted($this->path . '/' . $key)) { // otherwise we (re)load a node from backend but a child has been moved away already $nodesInBackend[] = $key; } } if ($update) { unset($oldNodes[$key]); } } else { //property or meta information /* Property type declarations start with :, the value then is * the type string from the NodeType constants. We skip that and * look at the type when we encounter the value of the property. * * If its a binary data, we only get the type declaration and * no data. Then the $value of the type declaration is not the * type string for binary, but the number of bytes of the * property - resp. array of number of bytes. * * The magic property ::NodeIteratorSize tells this node has no * children. Ignore that info for now. We might optimize with * this info once we do prefetch nodes. */ if (0 === strpos($key, ':')) { if ((is_int($value) || is_array($value)) && $key != '::NodeIteratorSize') { // This is a binary property and we just got its length with no data $key = substr($key, 1); if (!isset($rawData->{$key})) { $binaries[$key] = $value; if ($update) { unset($oldProperties[$key]); } if (isset($this->properties[$key])) { // refresh existing binary, this will only happen in update // only update length if (!($keepChanges && $this->properties[$key]->isModified())) { $this->properties[$key]->_setLength($value); if ($this->properties[$key]->isDirty()) { $this->properties[$key]->setClean(); } } } else { // this will always fall into the creation mode $this->_setProperty($key, $value, PropertyType::BINARY, true); } } } //else this is a type declaration //skip this entry (if its binary, its already processed continue; } if ($update && array_key_exists($key, $this->properties)) { unset($oldProperties[$key]); $prop = $this->properties[$key]; if ($keepChanges && $prop->isModified()) { continue; } } elseif ($update && array_key_exists($key, $this->deletedProperties)) { if ($keepChanges) { // keep the delete continue; } else { // restore the property $this->properties[$key] = $this->deletedProperties[$key]; $this->properties[$key]->setClean(); // now let the loop update the value. no need to talk to ObjectManager as it // does not store property deletions } } switch ($key) { case 'jcr:index': $this->index = $value; break; case 'jcr:primaryType': $this->primaryType = $value; // type information is exposed as property too, // although there exist more specific methods $this->_setProperty('jcr:primaryType', $value, PropertyType::NAME, true); break; case 'jcr:mixinTypes': // type information is exposed as property too, // although there exist more specific methods $this->_setProperty($key, $value, PropertyType::NAME, true); break; // OPTIMIZE: do not instantiate properties until needed // OPTIMIZE: do not instantiate properties until needed default: if (isset($rawData->{':' . $key})) { /* * this is an inconsistency between jackrabbit and * dbal transport: jackrabbit has type name, dbal * delivers numeric type. * we should eventually fix the format returned by * transport and either have jackrabbit transport * do the conversion or let dbal store a string * value instead of numerical. */ $type = is_numeric($rawData->{':' . $key}) ? $rawData->{':' . $key} : PropertyType::valueFromName($rawData->{':' . $key}); } else { $type = $this->valueConverter->determineType($value); } $this->_setProperty($key, $value, $type, true); break; } } } if ($update) { if ($keepChanges) { // we keep changes. merge new nodes to the right place $previous = null; $newFromBackend = array_diff($nodesInBackend, array_intersect($this->nodes, $nodesInBackend)); foreach ($newFromBackend as $name) { $pos = array_search($name, $nodesInBackend); if (is_array($this->originalNodesOrder)) { // update original order to send the correct reorderings array_splice($this->originalNodesOrder, $pos, 0, $name); } if ($pos === 0) { array_unshift($this->nodes, $name); } else { // do we find the predecessor of the new node in the list? $insert = array_search($nodesInBackend[$pos - 1], $this->nodes); if (false !== $insert) { array_splice($this->nodes, $insert + 1, 0, $name); } else { // failed to find predecessor, add to the end $this->nodes[] = $name; } } } } else { // discard changes, just overwrite node list $this->nodes = $nodesInBackend; $this->originalNodesOrder = null; } foreach ($oldProperties as $name => $property) { if (!($keepChanges && $property->isNew())) { // may not call remove(), we don't want another delete with // the backend to be attempted $this->properties[$name]->setDeleted(); unset($this->properties[$name]); } } // notify nodes that where not received again that they disappeared foreach ($oldNodes as $name => $index) { if ($this->objectManager->purgeDisappearedNode($this->path . '/' . $name, $keepChanges)) { // drop, it was not a new child if ($keepChanges) { // otherwise we overwrote $this->nodes with the backend $id = array_search($name, $this->nodes); if (false !== $id) { unset($this->nodes[$id]); } } } } } else { // new node loaded from backend $this->nodes = $nodesInBackend; } }
/** * {@inheritDoc} */ public function denormalize($data, $class, $format = null, array $context = array()) { if (!$data) { throw new \InvalidArgumentException('Editor returned nothing .. nodes must have at least one property (i.e. the jcr:primaryType property)'); } if (!isset($context['node'])) { throw new \InvalidArgumentException(sprintf('You must provide the PHPCR node instance to update in the context using the "node" key.')); } $node = $context['node']; $errors = array(); // Update / remove existing properties foreach ($node->getProperties() as $property) { if (false === $this->isPropertyEditable($property)) { continue; } try { if (!isset($data[$property->getName()])) { $property->remove(); continue; } $datum = $this->normalizeDatum($data[$property->getName()]); $typeValue = isset($datum['type']) ? PropertyType::valueFromName($datum['type']) : null; if (isset($datum['value'])) { // if the type or the value is differnet, update the property if ($datum['value'] != $property->getValue() || $typeValue != $property->getType()) { // setValue doesn't like being passed a null value as a type ... if ($typeValue !== null) { $property->setValue($datum['value'], $typeValue); } else { $property->setValue($datum['value']); } } } } catch (\Exception $e) { $errors[] = $e->getMessage(); } unset($data[$property->getName()]); } // Add new properties foreach ($data as $pName => $datum) { $datum = $this->normalizeDatum($datum); $pValue = isset($datum['value']) ? $datum['value'] : null; $pType = isset($datum['type']) ? PropertyType::valueFromName($datum['type']) : null; if ($pValue !== null) { $node->setProperty($pName, $pValue, $pType); } } if (count($errors) > 0) { throw new InvalidArgumentException(sprintf('Errors encountered during denormalization: %s', implode($errors, "\n"))); } }
protected function parseCastLiteral($token) { if (!$this->scanner->tokenIs($token, 'CAST')) { throw new \LogicException('parseCastLiteral when not a CAST'); } $this->scanner->expectToken('('); $token = $this->scanner->fetchNextToken(); $quoteString = false; if (substr($token, 0, 1) === '\'') { $quoteString = "'"; } elseif (substr($token, 0, 1) === '"') { $quoteString = '"'; } if ($quoteString) { while (substr($token, -1) !== $quoteString) { $nextToken = $this->scanner->fetchNextToken(); if ('' === $nextToken) { break; } $token .= $nextToken; } if (substr($token, -1) !== $quoteString) { throw new InvalidQueryException("Syntax error: unterminated quoted string '{$token}' in '{$this->sql2}'"); } $token = substr($token, 1, -1); $token = str_replace('\\' . $quoteString, $quoteString, $token); } $this->scanner->expectToken('AS'); $type = $this->scanner->fetchNextToken(); try { $typeValue = PropertyType::valueFromName($type); } catch (\InvalidArgumentException $e) { throw new InvalidQueryException("Syntax error: attempting to cast to an invalid type '{$type}'"); } $this->scanner->expectToken(')'); try { $token = $this->valueConverter->convertType($token, $typeValue, PropertyType::STRING); } catch (\Exception $e) { throw new InvalidQueryException("Syntax error: attempting to cast string '{$token}' to type '{$type}'"); } return $token; }
/** * "Decode" PHPCR property to MongoDB property * * @param $property * @return array|null * * @throws \Exception */ private function decodeProperty(PropertyInterface $property) { $path = $property->getPath(); $path = $this->validatePath($path); $name = explode('/', $path); $name = end($name); if ($name == 'jcr:uuid' || $name == 'jcr:primaryType') { return null; } if (!$property->isModified() && !$property->isNew()) { return null; } $propType = $property->getType(); $propNode = $property->getNode(); if (($propType == PropertyType::REFERENCE || $propType == PropertyType::WEAKREFERENCE) && ($propNode instanceof NodeInterface && !$propNode->isNodeType('mix:referenceable'))) { throw new ValueFormatException('Node ' . $property->getNode()->getPath() . ' is not referenceable.'); } $isMultiple = $property->isMultiple(); $typeId = $property->getType(); $type = PropertyType::nameFromValue($typeId); $data = array('multi' => $isMultiple, 'name' => $property->getName(), 'type' => $type); $binaryData = null; switch ($typeId) { case PropertyType::NAME: case PropertyType::URI: case PropertyType::WEAKREFERENCE: case PropertyType::REFERENCE: case PropertyType::PATH: $values = $property->getString(); break; case PropertyType::DECIMAL: $values = $property->getDecimal(); break; case PropertyType::STRING: $values = $property->getString(); break; case PropertyType::BOOLEAN: $values = $property->getBoolean(); break; case PropertyType::LONG: $values = $property->getLong(); break; case PropertyType::BINARY: if ($property->isMultiple()) { foreach ((array) $property->getBinary() as $binary) { $binary = stream_get_contents($binary); $binaryData[] = $binary; $values[] = strlen($binary); } } else { $binary = stream_get_contents($property->getBinary()); $binaryData[] = $binary; $values = strlen($binary); } break; case PropertyType::DATE: if ($property->isMultiple()) { $dates = $property->getDate() ?: new \DateTime('now'); foreach ((array) $dates as $date) { $value = array('date' => new \MongoDate($date->getTimestamp()), 'timezone' => $date->getTimezone()->getName()); $values[] = $value; } } else { $date = $property->getDate() ?: new \DateTime('now'); $values = array('date' => new \MongoDate($date->getTimestamp()), 'timezone' => $date->getTimezone()->getName()); } break; case PropertyType::DOUBLE: $values = $property->getDouble(); break; } if ($isMultiple) { $data['value'] = array(); foreach ((array) $values as $value) { $this->assertValidPropertyValue($data['type'], $value, $path); $data['value'][] = $value; } } else { $this->assertValidPropertyValue($data['type'], $values, $path); $data['value'] = $values; } if ($binaryData) { try { foreach ($binaryData as $idx => $binary) { $grid = $this->db->getGridFS(); $grid->getMongoCollection()->storeBytes($binary, array('path' => $path, 'w_id' => $this->workspaceId, 'idx' => $idx)); } } catch (\Exception $e) { throw $e; } } return $data; }
/** * Stores a property to the given absolute path * * @param string $path Absolute path to identify a specific property. * @param \PHPCR\PropertyInterface * @return bool true on success * * @throws \PHPCR\RepositoryException if not logged in */ public function storeProperty($path, \PHPCR\PropertyInterface $property) { $this->ensureAbsolutePath($path); $type = \PHPCR\PropertyType::nameFromValue($property->getType()); $request = $this->getRequest(Request::PUT, $path); $nativeValue = $property->getNativeValue(); if ($property->getName() === 'jcr:mixinTypes') { $uri = $this->normalizeUri(dirname($path) === '\\' ? '/' : dirname($path)); $request->setUri($uri); $request->setMethod(Request::PROPPATCH); $body = '<?xml version="1.0" encoding="UTF-8"?>' . '<D:propertyupdate xmlns:D="DAV:">' . '<D:set>' . '<D:prop>' . '<dcr:mixinnodetypes xmlns:dcr="http://www.day.com/jcr/webdav/1.0">'; foreach ($nativeValue as $value) { $body .= '<dcr:nodetype><dcr:nodetypename>' . $value . '</dcr:nodetypename></dcr:nodetype>'; } $body .= '</dcr:mixinnodetypes>' . '</D:prop>' . '</D:set>' . '</D:propertyupdate>'; } elseif (is_array($nativeValue)) { $body = '<?xml version="1.0" encoding="UTF-8"?>' . '<jcr:values xmlns:jcr="http://www.day.com/jcr/webdav/1.0">'; foreach ($nativeValue as $value) { $body .= '<jcr:value jcr:type="' . $type . '">' . $this->propertyToXmlString($value, $type) . '</jcr:value>'; } $body .= '</jcr:values>'; } else { $body = $this->propertyToRawString($nativeValue, $type); $request->setContentType('jcr-value/' . strtolower($type)); } $request->setBody($body); $request->execute(); return true; }
/** * Executes all document updates * * @param array $documents array of all to be updated documents * @param boolean $dispatchEvents if to dispatch events */ private function executeUpdates($documents, $dispatchEvents = true) { foreach ($documents as $oid => $document) { if (!$this->contains($oid)) { continue; } $class = $this->dm->getClassMetadata(get_class($document)); $node = $this->session->getNode($this->getDocumentId($document)); if ($this->writeMetadata) { $this->documentClassMapper->writeMetadata($this->dm, $node, $class->name); } if ($dispatchEvents) { if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::preUpdate)) { $this->eventListenersInvoker->invoke($class, Event::preUpdate, $document, new PreUpdateEventArgs($document, $this->dm, $this->documentChangesets[$oid]), $invoke); $this->changesetComputed = array_diff($this->changesetComputed, array($oid)); $this->computeChangeSet($class, $document); } } $fields = isset($this->documentChangesets[$oid]['fields']) ? $this->documentChangesets[$oid]['fields'] : array(); foreach ($fields as $fieldName => $data) { $fieldValue = $data[1]; // PHPCR does not validate nullable unless we would start to // generate custom node types, which we at the moment don't. // the ORM can delegate this validation to the relational database // that is using a strict schema. // do this after the preUpdate events to give listener a last // chance to provide values if (null === $fieldValue && in_array($fieldName, $class->fieldMappings) && !$class->isNullable($fieldName) && !$this->isAutocreatedProperty($class, $fieldName)) { throw new PHPCRException(sprintf('Field "%s" of class "%s" is not nullable', $fieldName, $class->name)); } // Ignore translatable fields (they will be persisted by the translation strategy) if (in_array($fieldName, $class->translatableFields)) { continue; } $mapping = $class->mappings[$fieldName]; if (in_array($fieldName, $class->fieldMappings)) { $type = PropertyType::valueFromName($mapping['type']); if ($mapping['multivalue']) { $value = empty($fieldValue) ? null : ($fieldValue instanceof Collection ? $fieldValue->toArray() : $fieldValue); if ($value && isset($mapping['assoc'])) { $value = $this->processAssoc($node, $mapping, $value); } } else { $value = $fieldValue; } $node->setProperty($mapping['property'], $value, $type); } elseif ($mapping['type'] === $class::MANY_TO_ONE || $mapping['type'] === $class::MANY_TO_MANY) { if (!$this->writeMetadata) { continue; } if ($node->hasProperty($mapping['property']) && is_null($fieldValue)) { $node->getProperty($mapping['property'])->remove(); if (isset($mapping['assoc'])) { $this->removeAssoc($node, $mapping); } continue; } switch ($mapping['strategy']) { case 'hard': $strategy = PropertyType::REFERENCE; break; case 'path': $strategy = PropertyType::PATH; break; default: $strategy = PropertyType::WEAKREFERENCE; break; } if ($mapping['type'] === $class::MANY_TO_MANY) { if (isset($fieldValue)) { $refNodesIds = array(); foreach ($fieldValue as $fv) { if ($fv === null) { continue; } $associatedNode = $this->session->getNode($this->getDocumentId($fv)); if ($strategy === PropertyType::PATH) { $refNodesIds[] = $associatedNode->getPath(); } else { $refClass = $this->dm->getClassMetadata(get_class($fv)); $this->setMixins($refClass, $associatedNode, $fv); if (!$associatedNode->isNodeType('mix:referenceable')) { throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), ClassUtils::getClass($fv))); } $refNodesIds[] = $associatedNode->getIdentifier(); } } $refNodesIds = empty($refNodesIds) ? null : $refNodesIds; $node->setProperty($mapping['property'], $refNodesIds, $strategy); } } elseif ($mapping['type'] === $class::MANY_TO_ONE) { if (isset($fieldValue)) { $associatedNode = $this->session->getNode($this->getDocumentId($fieldValue)); if ($strategy === PropertyType::PATH) { $node->setProperty($fieldName, $associatedNode->getPath(), $strategy); } else { $refClass = $this->dm->getClassMetadata(get_class($fieldValue)); $this->setMixins($refClass, $associatedNode, $document); if (!$associatedNode->isNodeType('mix:referenceable')) { throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), ClassUtils::getClass($fieldValue))); } $node->setProperty($mapping['property'], $associatedNode->getIdentifier(), $strategy); } } } } elseif ('referrers' === $mapping['type']) { if (isset($fieldValue)) { /* * each document in referrers field is supposed to * reference this document, so we have to update its * referencing property to contain the uuid of this * document */ foreach ($fieldValue as $fv) { if ($fv === null) { continue; } if (!$fv instanceof $mapping['referringDocument']) { throw new PHPCRException(sprintf("%s is not an instance of %s for document %s field %s", self::objToStr($fv, $this->dm), $mapping['referencedBy'], self::objToStr($document, $this->dm), $mapping['fieldName'])); } $referencingNode = $this->session->getNode($this->getDocumentId($fv)); $referencingMeta = $this->dm->getClassMetadata($mapping['referringDocument']); $referencingField = $referencingMeta->getAssociation($mapping['referencedBy']); $uuid = $node->getIdentifier(); $strategy = $referencingField['strategy'] == 'weak' ? PropertyType::WEAKREFERENCE : PropertyType::REFERENCE; switch ($referencingField['type']) { case ClassMetadata::MANY_TO_ONE: $ref = $referencingMeta->getFieldValue($fv, $referencingField['fieldName']); if ($ref !== null && $ref !== $document) { throw new PHPCRException(sprintf('Conflicting settings for referrer and reference: Document %s field %s points to %s but document %s has set first document as referrer on field %s', self::objToStr($fv, $this->dm), $referencingField['fieldName'], self::objToStr($ref, $this->dm), self::objToStr($document, $this->dm), $mapping['fieldName'])); } // update the referencing document field to point to this document $referencingMeta->setFieldValue($fv, $referencingField['fieldName'], $document); // and make sure the reference is not deleted in this change because the field could be null unset($this->documentChangesets[spl_object_hash($fv)]['fields'][$referencingField['fieldName']]); // store the change in PHPCR $referencingNode->setProperty($referencingField['property'], $uuid, $strategy); break; case ClassMetadata::MANY_TO_MANY: /** @var $collection ReferenceManyCollection */ $collection = $referencingMeta->getFieldValue($fv, $referencingField['fieldName']); if ($collection instanceof PersistentCollection && $collection->isDirty()) { throw new PHPCRException(sprintf('You may not modify the reference and referrer collections of interlinked documents as this is ambiguous. Reference %s on document %s and referrers %s on document %s are both modified', self::objToStr($fv, $this->dm), $referencingField['fieldName'], self::objToStr($document, $this->dm), $mapping['fieldName'])); } if ($collection) { // make sure the reference is not deleted in this change because the field could be null unset($this->documentChangesets[spl_object_hash($fv)]['fields'][$referencingField['fieldName']]); } else { $collection = new ReferenceManyCollection($this->dm, $fv, $referencingField['property'], array($node), $class->name); $referencingMeta->setFieldValue($fv, $referencingField['fieldName'], $collection); } if ($referencingNode->hasProperty($referencingField['property'])) { if (!in_array($uuid, $referencingNode->getProperty($referencingField['property'])->getString())) { if (!$collection instanceof PersistentCollection || !$collection->isDirty()) { // update the reference collection: add us to it $collection->add($document); } // store the change in PHPCR $referencingNode->getProperty($referencingField['property'])->addValue($uuid); // property should be correct type already } } else { // store the change in PHPCR $referencingNode->setProperty($referencingField['property'], array($uuid), $strategy); } // avoid confusion later, this change to the reference collection is already saved $collection->setDirty(false); break; default: // in class metadata we only did a santiy check but not look at the actual mapping throw new MappingException(sprintf('Field "%s" of document "%s" is not a reference field. Error in referrer annotation: ' . self::objToStr($document, $this->dm), $mapping['referencedBy'], ClassUtils::getClass($fv))); } } } } elseif ('child' === $mapping['type']) { if ($fieldValue === null && $node->hasNode($mapping['nodeName'])) { $child = $node->getNode($mapping['nodeName']); $childDocument = $this->getOrCreateDocument(null, $child); $this->purgeChildren($childDocument); $child->remove(); } } } if (!empty($this->documentChangesets[$oid]['reorderings'])) { foreach ($this->documentChangesets[$oid]['reorderings'] as $reorderings) { foreach ($reorderings as $srcChildRelPath => $destChildRelPath) { $node->orderBefore($srcChildRelPath, $destChildRelPath); } } } $this->doSaveTranslation($document, $node, $class); if ($dispatchEvents) { if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::postUpdate)) { $this->eventListenersInvoker->invoke($class, Event::postUpdate, $document, new LifecycleEventArgs($document, $this->dm), $invoke); } } } }
/** * Quote a string for inclusion in an SQL2 query * * @see \PHPCR\PropertyType * @param string $val * @param int $type * @return string */ public function quote($val, $type = PropertyType::STRING) { if (null !== $type) { $val = PropertyType::convertType($val, $type); } return "'" . str_replace("'", "''", $val) . "'"; }
/** * @Given /^the property "([^"]*)" should have type "([^"]*)" and value "([^"]*)"$/ */ public function thePropertyShouldHaveTypeAndValue($arg1, $arg2, $arg3) { $session = $this->getSession(); $property = $session->getItem($arg1); if (!$property instanceof PropertyInterface) { throw new \InvalidArgumentException(sprintf('Item at "%s" is not a property', $arg1)); } \PHPUnit_Framework_Assert::assertEquals($arg2, PropertyType::nameFromValue($property->getType())); \PHPUnit_Framework_Assert::assertEquals($arg3, $property->getValue()); }
/** * {@inheritDoc} * * @api */ public function getPropertyValue($name, $type = null) { $this->checkState(); $val = $this->getProperty($name)->getValue(); if (!is_null($type)) { $val = PropertyType::convertType($val, $type); } return $val; }
/** * @expectedException \PHPCR\ValueFormatException */ public function testDetermineTypeNull() { PropertyType::determineType(null); }
/** * The property type is delimited by parentheses ('*' is a synonym for UNDEFINED). * If this element is absent, STRING is assumed. A '?' indicates that this * attribute is a variant. * * PropertyType ::= '(' ('STRING' | 'BINARY' | 'LONG' | 'DOUBLE' | * 'BOOLEAN' | 'DATE' | 'NAME' | 'PATH' | * 'REFERENCE' | 'WEAKREFERENCE' | * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | * '?') ')' */ protected function parsePropertyType(PropertyDefinitionTemplateInterface $property) { $types = array("STRING", "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI", "UNDEFINED", "*", "?"); if (!$this->checkTokenIn(Token::TK_IDENTIFIER, $types, true)) { throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $this->tokenQueue->get()->getData())); } $data = $this->tokenQueue->get()->getData(); $this->expectToken(Token::TK_SYMBOL, ')'); $property->setRequiredType(PropertyType::valueFromName($data)); }
protected function getMimePart($name, $value, $mime_boundary) { $data = ''; $eol = "\r\n"; if (is_array($value)) { if (is_array($value[0])) { foreach ($value[0] as $v) { $data .= $this->getMimePart($name, array($v, $value[1]), $mime_boundary); } return $data; } $data .= '--' . $mime_boundary . $eol; if (is_resource($value[0])) { $data .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $name . '"' . $eol; $data .= 'Content-Type: jcr-value/' . strtolower(PropertyType::nameFromValue($value[1])) . '; charset=UTF-8' . $eol; $data .= 'Content-Transfer-Encoding: binary' . $eol . $eol; $data .= stream_get_contents($value[0]) . $eol; fclose($value[0]); } else { $data .= 'Content-Disposition: form-data; name="' . $name . '"' . $eol; $data .= 'Content-Type: jcr-value/' . strtolower(PropertyType::nameFromValue($value[1])) . '; charset=UTF-8' . $eol; $data .= 'Content-Transfer-Encoding: 8bit' . $eol . $eol; switch ($value[1]) { case PropertyType::DATE: $data .= $this->valueConverter->convertType($value[0], PropertyType::STRING); break; default: $data .= $value[0]; } $data .= $eol; } } else { if (is_array($value)) { foreach ($value as $v) { $data .= $this->getMimePart($name, $v, $mime_boundary); } return $data; } $data .= '--' . $mime_boundary . $eol; $data .= 'Content-Disposition: form-data; name="' . $name . '"' . $eol; $data .= 'Content-Type: text/plain; charset=UTF-8' . $eol; $data .= 'Content-Transfer-Encoding: 8bit' . $eol . $eol; //$data .= '--' . $mime_boundary . $eol; $data .= $value . $eol; } return $data; }
private function writeProperties($properties) { if (null === $properties) { // getDeclaredPropertyDefinitions is allowed to return null on // newly created node type definitions return ''; } $s = ''; /** @var $property PropertyDefinitionInterface */ foreach ($properties as $property) { $this->checkNamespace($property->getName()); $s .= '- ' . $property->getName(); if ($property->getRequiredType()) { $s .= ' (' . PropertyType::nameFromValue($property->getRequiredType()) . ')'; } $s .= "\n"; if ($property->getDefaultValues()) { $s .= "= '" . implode("', '", $property->getDefaultValues()) . "'\n"; } $attributes = ''; if ($property->isMandatory()) { $attributes .= 'mandatory '; } if ($property->isAutoCreated()) { $attributes .= 'autocreated '; } if ($property->isProtected()) { $attributes .= 'protected '; } if ($property->isMultiple()) { $attributes .= 'multiple '; } if ($property->getAvailableQueryOperators()) { $attributes .= implode("', '", $property->getAvailableQueryOperators()); } if (!$property->isFullTextSearchable()) { $attributes .= 'nofulltext '; } if (!$property->isQueryOrderable()) { $attributes .= 'noqueryorder '; } if (OnParentVersionAction::COPY !== $property->getOnParentVersion()) { $attributes .= OnParentVersionAction::nameFromValue($property->getOnParentVersion()) . ' '; } if ($attributes) { $s .= trim($attributes) . "\n"; } if ($property->getValueConstraints()) { $s .= "< '" . implode("', '", $property->getValueConstraints()) . "'\n"; } } return $s; }
/** * Executes all document updates * * @param array $documents array of all to be updated documents * @param boolean $dispatchEvents if to dispatch events */ private function executeUpdates($documents, $dispatchEvents = true) { foreach ($documents as $oid => $document) { if (!$this->contains($oid)) { continue; } $class = $this->dm->getClassMetadata(get_class($document)); $node = $this->session->getNode($this->getDocumentId($document)); if ($this->writeMetadata) { $this->documentClassMapper->writeMetadata($this->dm, $node, $class->name); } if ($dispatchEvents) { if (isset($class->lifecycleCallbacks[Event::preUpdate])) { $class->invokeLifecycleCallbacks(Event::preUpdate, $document); $this->computeChangeSet($class, $document); } if ($this->evm->hasListeners(Event::preUpdate)) { $this->evm->dispatchEvent(Event::preUpdate, new LifecycleEventArgs($document, $this->dm)); $this->computeChangeSet($class, $document); } } foreach ($this->documentChangesets[$oid] as $fieldName => $fieldValue) { // Ignore translatable fields (they will be persisted by the translation strategy) if (in_array($fieldName, $class->translatableFields)) { continue; } if (isset($class->fieldMappings[$fieldName])) { $type = PropertyType::valueFromName($class->fieldMappings[$fieldName]['type']); if ($class->fieldMappings[$fieldName]['multivalue']) { $value = $fieldValue === null ? null : $fieldValue->toArray(); $node->setProperty($class->fieldMappings[$fieldName]['name'], $value, $type); } else { $node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type); } } elseif (isset($class->associationsMappings[$fieldName]) && $this->writeMetadata) { if ($node->hasProperty($class->associationsMappings[$fieldName]['fieldName']) && is_null($fieldValue)) { $node->getProperty($class->associationsMappings[$fieldName]['fieldName'])->remove(); continue; } $type = $class->associationsMappings[$fieldName]['weak'] ? PropertyType::WEAKREFERENCE : PropertyType::REFERENCE; if ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_MANY) { if (isset($fieldValue)) { $refNodesIds = array(); foreach ($fieldValue as $fv) { if ($fv === null) { continue; } $associatedNode = $this->session->getNode($this->getDocumentId($fv)); $refClass = $this->dm->getClassMetadata(get_class($fv)); $this->setMixins($refClass, $associatedNode); if (!$associatedNode->isNodeType('mix:referenceable')) { throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), get_class($fv))); } $refNodesIds[] = $associatedNode->getIdentifier(); } if (!empty($refNodesIds)) { $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $refNodesIds, $type); } } } elseif ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_ONE) { if (isset($fieldValue)) { $associatedNode = $this->session->getNode($this->getDocumentId($fieldValue)); $refClass = $this->dm->getClassMetadata(get_class($fieldValue)); $this->setMixins($refClass, $associatedNode); if (!$associatedNode->isNodeType('mix:referenceable')) { throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), get_class($fieldValue))); } $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $associatedNode->getIdentifier(), $type); } } } elseif (isset($class->childMappings[$fieldName])) { if ($fieldValue === null) { if ($node->hasNode($class->childMappings[$fieldName]['name'])) { $child = $node->getNode($class->childMappings[$fieldName]['name']); $childDocument = $this->createDocument(null, $child); $this->purgeChildren($childDocument); $child->remove(); } } elseif ($this->originalData[$oid][$fieldName] && $this->originalData[$oid][$fieldName] !== $fieldValue) { throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the DocumentManager::move() or PHPCR\\Session::copy() operations for this.'); } } } $this->doSaveTranslation($document, $node, $class); if ($dispatchEvents) { if (isset($class->lifecycleCallbacks[Event::postUpdate])) { $class->invokeLifecycleCallbacks(Event::postUpdate, $document); } if ($this->evm->hasListeners(Event::postUpdate)) { $this->evm->dispatchEvent(Event::postUpdate, new LifecycleEventArgs($document, $this->dm)); } } } }
/** * Quote a string for inclusion in an SQL2 query * * @see \PHPCR\PropertyType * @param string $val * @param int $type * @return string */ public function quote($val, $type = null) { if (null !== $type) { $val = PropertyType::convertType($val, $type); } return "'" . str_replace("'", "''", $this->escapeIllegalCharacters($val)) . "'"; }
/** * Internally used to set the value of the property without any notification * of changes nor state change. * * @param mixed $value * @param string $type * * @return void * * @see Property::setValue() * * @private */ public function _setValue($value, $type = PropertyType::UNDEFINED) { if (is_null($value)) { $this->remove(); return; } if (!is_integer($type)) { // @codeCoverageIgnoreStart throw new InvalidArgumentException("The type has to be one of the numeric constants defined in PHPCR\\PropertyType. {$type}"); // @codeCoverageIgnoreEnd } if ($this->isNew()) { $this->isMultiple = is_array($value); } if (is_array($value) && !$this->isMultiple) { throw new ValueFormatException('Can not set a single value property (' . $this->name . ') with an array of values'); } //TODO: check if changing type allowed. /* * if ($type !== null && ! canHaveType($type)) { * throw new ConstraintViolationException("Can not set this property to type ".PropertyType::nameFromValue($type)); * } */ if (PropertyType::UNDEFINED === $type) { $type = PropertyType::determineType(is_array($value) ? reset($value) : $value); } $targettype = $this->type; if ($this->type !== $type) { /* TODO: find out with node type definition if the new type is allowed if (canHaveType($type)) { */ $targettype = $type; /* } else { //convert to an allowed type. if the current type is defined $targettype = $this->type; } */ } $value = PropertyType::convertType($value, $targettype, $type); if (PropertyType::BINARY === $targettype) { $stat = fstat($value); //TODO: read file into local context? fstat not available on all streams $this->length = $stat['size']; } $this->type = $targettype; $this->value = $value; }
/** * Import document in system view * * @param NodeInterface $parentNode * @param NamespaceRegistryInterface $ns * @param FilteredXMLReader $xml * @param int $uuidBehavior * @param array $namespaceMap hashmap of prefix => uri for namespaces in the document */ private static function importSystemView(NodeInterface $parentNode, NamespaceRegistryInterface $ns, FilteredXMLReader $xml, $uuidBehavior, $namespaceMap = array()) { while ($xml->moveToNextAttribute()) { if ('xmlns' == $xml->prefix) { try { $prefix = $ns->getPrefix($xml->value); } catch (NamespaceException $e) { $prefix = $xml->localName; $ns->registerNamespace($prefix, $xml->value); } // @codeCoverageIgnoreStart if ('jcr' == $prefix && 'jcr' != $xml->localName) { throw new RepositoryException('Can not handle a document where the {http://www.jcp.org/jcr/1.0} namespace is not mapped to jcr'); } if ('nt' == $prefix && 'nt' != $xml->localName) { throw new RepositoryException('Can not handle a document where the {http://www.jcp.org/jcr/nt/1.0} namespace is not mapped to nt'); } // @codeCoverageIgnoreEnd $namespaceMap[$xml->localName] = $prefix; } elseif (NamespaceRegistryInterface::NAMESPACE_SV == $xml->namespaceURI && 'name' == $xml->localName) { $nodename = $xml->value; } } if (!isset($nodename)) { throw new InvalidSerializedDataException('there was no sv:name attribute in an element'); } // now get jcr:primaryType if (!$xml->read()) { throw new InvalidSerializedDataException('missing information to create node'); } if ('property' != $xml->localName || NamespaceRegistryInterface::NAMESPACE_SV != $xml->namespaceURI) { throw new InvalidSerializedDataException('first child of node must be sv:property for jcr:primaryType. Found {' . $xml->namespaceURI . '}' . $xml->localName . '="' . $xml->value . '"' . $xml->nodeType); } if (!$xml->moveToAttributeNs('name', NamespaceRegistryInterface::NAMESPACE_SV) || 'jcr:primaryType' != $xml->value) { throw new InvalidSerializedDataException('first child of node must be sv:property for jcr:primaryType. Found {' . $xml->namespaceURI . '}' . $xml->localName . '="' . $xml->value . '"'); } $xml->read(); // value child of property jcr:primaryType $xml->read(); // text content $nodetype = $xml->value; $nodename = self::cleanNamespace($nodename, $namespaceMap); $properties = array(); $xml->read(); // </value> $xml->read(); // </property> $xml->read(); // next thing // read the properties of the node. they must come first. while (XMLReader::END_ELEMENT != $xml->nodeType && 'property' == $xml->localName) { $xml->moveToAttributeNs('name', NamespaceRegistryInterface::NAMESPACE_SV); $name = $xml->value; $xml->moveToAttributeNs('type', NamespaceRegistryInterface::NAMESPACE_SV); $type = PropertyType::valueFromName($xml->value); if ($xml->moveToAttributeNs('multiple', NamespaceRegistryInterface::NAMESPACE_SV)) { $multiple = strcasecmp($xml->value, 'true') === 0; } else { $multiple = false; } $values = array(); // go to the value child. if empty property, brings us to closing // property tag. if self-closing, brings us to the next property or // node closing tag $xml->read(); while ('value' == $xml->localName) { if ($xml->isEmptyElement) { $values[] = ''; } else { $xml->read(); if (XMLReader::END_ELEMENT == $xml->nodeType) { // this is an empty tag $values[] = ''; } else { $values[] = PropertyType::BINARY == $type ? base64_decode($xml->value) : $xml->value; $xml->read(); // consume the content } } $xml->read(); // consume closing tag } if (!$multiple && count($values) == 1) { $values = reset($values); // unbox if it does not need to be multivalue } $name = self::cleanNamespace($name, $namespaceMap); $properties[$name] = array('values' => $values, 'type' => $type); /* only consume closing property, but not the next element if we * had an empty multiple property with no value children at all * and don't consume the closing node tag after a self-closing * empty property */ if (XMLReader::END_ELEMENT == $xml->nodeType && 'property' == $xml->localName) { $xml->read(); } } $node = self::addNode($parentNode, $nodename, $nodetype, $properties, $uuidBehavior); // if there are child nodes, they all come after the properties while (XMLReader::END_ELEMENT != $xml->nodeType && 'node' == $xml->localName) { self::importSystemView($node, $ns, $xml, $uuidBehavior, $namespaceMap); } if (XMLReader::END_ELEMENT != $xml->nodeType) { throw new InvalidSerializedDataException('Unexpected element "' . $xml->localName . '" type "' . $xml->nodeType . '" with content "' . $xml->value . '" after ' . $node->getPath()); } $xml->read(); // </node> }
/** * Seperate properties array into an xml and binary data. * * @param array $properties * * @return array ( * 'stringDom' => $stringDom, * 'numericalDom' => $numericalDom', * 'binaryData' => streams, * 'references' => array('type' => INT, 'values' => array(UUIDs))) */ private function propsToXML($properties) { $namespaces = array('mix' => "http://www.jcp.org/jcr/mix/1.0", 'nt' => "http://www.jcp.org/jcr/nt/1.0", 'xs' => "http://www.w3.org/2001/XMLSchema", 'jcr' => "http://www.jcp.org/jcr/1.0", 'sv' => "http://www.jcp.org/jcr/sv/1.0", 'rep' => "internal"); $doms = array('stringDom' => array(), 'numericalDom' => array()); $binaryData = $references = array(); foreach ($properties as $property) { $targetDoms = array('stringDom'); switch ($property->getType()) { case PropertyType::WEAKREFERENCE: case PropertyType::REFERENCE: $references[$property->getName()] = array('type' => $property->getType(), 'values' => $property->isMultiple() ? $property->getString() : array($property->getString())); case PropertyType::NAME: case PropertyType::URI: case PropertyType::PATH: case PropertyType::STRING: $values = $property->getString(); break; case PropertyType::DECIMAL: $values = $property->getDecimal(); $targetDoms[] = 'numericalDom'; break; case PropertyType::BOOLEAN: $values = array_map('intval', (array) $property->getBoolean()); break; case PropertyType::LONG: $values = $property->getLong(); $targetDoms[] = 'numericalDom'; break; case PropertyType::BINARY: if ($property->isNew() || $property->isModified()) { $values = array(); foreach ((array) $property->getValueForStorage() as $stream) { if (null === $stream) { $binary = ''; } else { $binary = stream_get_contents($stream); fclose($stream); } $binaryData[$property->getName()][] = $binary; $length = strlen($binary); $values[] = $length; } } else { $values = $property->getLength(); if (!$property->isMultiple() && empty($values)) { $values = array(0); } } break; case PropertyType::DATE: $values = $property->getDate(); if ($values instanceof \DateTime) { $values = array($values); } foreach ((array) $values as $key => $date) { if ($date instanceof \DateTime) { // do not modify the instance which is associated with the node. $date = clone $date; // normalize to UTC for storage. $date->setTimezone(new \DateTimeZone('UTC')); } $values[$key] = $date; } $values = $this->valueConverter->convertType($values, PropertyType::STRING); break; case PropertyType::DOUBLE: $values = $property->getDouble(); $targetDoms[] = 'numericalDom'; break; default: throw new RepositoryException('unknown type ' . $property->getType()); } foreach ($targetDoms as $targetDom) { $doms[$targetDom][] = array('name' => $property->getName(), 'type' => PropertyType::nameFromValue($property->getType()), 'multiple' => $property->isMultiple(), 'lengths' => (array) $property->getLength(), 'values' => $values); } } $ret = array('stringDom' => null, 'numericalDom' => null, 'binaryData' => $binaryData, 'references' => $references); foreach ($doms as $targetDom => $properties) { $dom = new \DOMDocument('1.0', 'UTF-8'); $rootNode = $dom->createElement('sv:node'); foreach ($namespaces as $namespace => $uri) { $rootNode->setAttribute('xmlns:' . $namespace, $uri); } $dom->appendChild($rootNode); foreach ($properties as $property) { /* @var $property Property */ $propertyNode = $dom->createElement('sv:property'); $propertyNode->setAttribute('sv:name', $property['name']); $propertyNode->setAttribute('sv:type', $property['type']); $propertyNode->setAttribute('sv:multi-valued', $property['multiple'] ? '1' : '0'); $lengths = (array) $property['lengths']; foreach ((array) $property['values'] as $key => $value) { $element = $propertyNode->appendChild($dom->createElement('sv:value')); $element->appendChild($dom->createTextNode($value)); if (isset($lengths[$key])) { $lengthAttribute = $dom->createAttribute('length'); $lengthAttribute->value = $lengths[$key]; $element->appendChild($lengthAttribute); } } $rootNode->appendChild($propertyNode); } if (count($properties)) { $ret[$targetDom] = $dom; } } return $ret; }
/** * Internally used to set the value of the property without any notification * of changes nor state change. * * @param mixed $value The value to set. * @param int|string $type PropertyType constant * @param boolean $constructor Whether this is called from the constructor. * * @see Property::setValue() * * @throws AccessDeniedException * @throws ItemNotFoundException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @throws VersionException * @throws InvalidArgumentException * @throws ValueFormatException * * @private */ public function _setValue($value, $type = PropertyType::UNDEFINED, $constructor = false) { if (null === $value) { $this->remove(); return; } if (is_string($type)) { $type = PropertyType::valueFromName($type); } elseif (!is_numeric($type)) { // @codeCoverageIgnoreStart throw new RepositoryException("INTERNAL ERROR -- No valid type specified ({$type})"); // @codeCoverageIgnoreEnd } else { //sanity check. this will throw InvalidArgumentException if $type is not a valid type PropertyType::nameFromValue($type); } if ($constructor || $this->isNew()) { $this->isMultiple = is_array($value); } if (is_array($value) && !$this->isMultiple) { throw new ValueFormatException('Can not set a single value property (' . $this->name . ') with an array of values'); } if ($this->isMultiple && is_scalar($value)) { throw new ValueFormatException('Can not set a multivalue property (' . $this->name . ') with a scalar value'); } if ($this->isMultiple) { foreach ($value as $key => $v) { if (null === $v) { unset($value[$key]); } } } //TODO: check if changing type allowed. /* * if ($type !== null && ! canHaveType($type)) { * throw new ConstraintViolationException("Can not set this property to type ".PropertyType::nameFromValue($type)); * } */ if (PropertyType::UNDEFINED === $type) { // avoid changing type of multivalue property with empty array if (!$this->isMultiple() || count($value) || PropertyType::UNDEFINED === $this->type) { $type = $this->valueConverter->determineType($value); } else { $type = $this->type; } } $targetType = $type; /* * TODO: find out with node type definition if the new type is allowed if ($this->type !== $type) { if (!canHaveType($type)) { //convert to an allowed type } } */ if (PropertyType::BINARY !== $targetType || $constructor && $this->isNew() || !$this->isNew() && PropertyType::UNDEFINED !== $this->type && $this->type !== $targetType) { $value = $this->valueConverter->convertType($value, $targetType, $constructor ? PropertyType::UNDEFINED : $type); } if (PropertyType::BINARY === $targetType) { if ($constructor && !$this->isNew()) { // reading a binary property from backend, we do not get the stream immediately but just the size if (is_array($value)) { $this->isMultiple = true; } $this->type = PropertyType::BINARY; $this->length = $value; $this->value = null; return; } if (is_array($value)) { $this->length = array(); foreach ($value as $v) { $stat = is_resource($v) ? fstat($v) : array('size' => -1); $this->length[] = $stat['size']; } } elseif (is_resource($value)) { $stat = fstat($value); $this->length = $stat['size']; } else { $this->length = -1; } } $this->type = $targetType; $this->value = $value; }
private function renderProperties($currentNode, $table, $spacers, $filter = null) { $properties = (array) $currentNode->getProperties($filter ?: null); $properties = $this->sort($properties); try { $primaryItem = $currentNode->getPrimaryItem(); } catch (ItemNotFoundException $e) { $primaryItem = null; } $nodeType = $currentNode->getPrimaryNodeType(); $propertyDefinitions = $nodeType->getDeclaredPropertyDefinitions(); $propertyNames = array(); foreach ($propertyDefinitions as $name => $propertyDefinition) { $propertyNames[$propertyDefinition->getName()] = $propertyDefinition; } $i = 0; foreach ($properties as $name => $property) { try { $i++; if (isset($propertyNames[$name])) { unset($propertyNames[$name]); } $valueCell = $this->formatter->formatValue($property); } catch (\Exception $e) { $valueCell = '<error>' . $e->getMessage() . '</error>'; } $table->addRow(array('<property>' . implode('', $spacers) . $name . '</property>', sprintf('<property-type>%s (%s)</property-type>', $this->formatter->getPropertyTypeName($property->getType()), implode(',', (array) $property->getLength()) ?: '0'), $valueCell)); } if ($this->showTemplate) { foreach ($propertyNames as $propertyName => $property) { $table->addRow(array('<templateproperty>' . implode('', $spacers) . '@' . $propertyName . '</templateproperty>', '<property-type>' . strtoupper(PropertyType::nameFromValue($property->getRequiredType())) . '</property-type>', '')); } } }
/** * Determine PropertyType from on variable type. * * This is most of the remainder of ValueFactory that is still needed. * * - if the given $value is a Node object, type will be REFERENCE, unless * $weak is set to true which results in WEAKREFERENCE * - if the given $value is a DateTime object, the type will be DATE. * * Note that string is converted to date exactly if it matches the jcr * formatting spec for dates (sYYYY-MM-DDThh:mm:ss.sssTZD) according to * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.6.4.3%20From%20DATE%20To * * @param mixed $value The variable we need to know the type of * @param boolean $weak When a Node is given as $value this can be given * as true to create a WEAKREFERENCE. * * @return int One of the type constants * * @throws ValueFormatException if the type can not be determined */ public function determineType($value, $weak = false) { return PropertyType::determineType($value, $weak); }