Exemplo n.º 1
0
 /**
  * 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');
     }
 }
Exemplo n.º 2
0
 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);
     }));
 }
Exemplo n.º 4
0
 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;
     }
 }
Exemplo n.º 5
0
 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);
     }
 }
Exemplo n.º 6
0
 /**
  * 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;
 }
Exemplo n.º 7
0
 /**
  * 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. In that case, you should pass an empty array
  * for $data and use setValue afterwards to let the type magic be handled.
  * Then multivalue is determined on setValue
  *
  * For binary properties, the value is the length of the data(s), not the
  * data itself.
  *
  * @param FactoryInterface $factory the object factory
  * @param array $data array with fields <tt>type</tt> (integer or string
  *      from PropertyType) and <tt>value</tt> (data for creating the
  *      property value - array for multivalue property)
  * @param string $path the absolute path of this item
  * @param Session $session the session instance
  * @param ObjectManager $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(FactoryInterface $factory, array $data, $path, Session $session, ObjectManager $objectManager, $new = false)
 {
     parent::__construct($factory, $path, $session, $objectManager, $new);
     $this->wrapBinaryStreams = $session->getRepository()->getDescriptor(Repository::JACKALOPE_OPTION_STREAM_WRAPPER);
     if (empty($data) && $new) {
         return;
     }
     if (!isset($data['value'])) {
         throw new InvalidArgumentException("Can't create property at {$path} without any data");
     }
     if (isset($data['type']) && PropertyType::UNDEFINED !== $data['type']) {
         $type = $data['type'];
         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);
         }
     } else {
         // we are creating a node
         $type = PropertyType::determineType(is_array($data['value']) ? reset($data['value']) : $data['value']);
     }
     $this->type = $type;
     if ($type == PropertyType::BINARY && !$new) {
         // reading a binary property from backend, we do not get the stream immediately but just the size
         if (is_array($data['value'])) {
             $this->isMultiple = true;
         }
         $this->length = $data['value'];
         $this->value = null;
         return;
     }
     if (is_array($data['value'])) {
         $this->isMultiple = true;
         $this->value = array();
         foreach ($data['value'] as $value) {
             $this->value[] = PropertyType::convertType($value, $type);
         }
     } elseif (null !== $data['value']) {
         $this->value = PropertyType::convertType($data['value'], $type);
     } else {
         // @codeCoverageIgnoreStart
         throw new RepositoryException('INTERNAL ERROR -- data[value] may not be null');
         // @codeCoverageIgnoreEnd
     }
 }
Exemplo n.º 8
0
    public static function xmlToProps($xml, ValueConverter $valueConverter, $filter = null)
    {
        $data = new \stdClass();

        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->loadXML($xml);

        foreach ($dom->getElementsByTagNameNS('http://www.jcp.org/jcr/sv/1.0', 'property') as $propertyNode) {
            $name = $propertyNode->getAttribute('sv:name');

            // only return the properties that pass through the filter callback
            if (null !== $filter && is_callable($filter) && false === $filter($name)) {
                continue;
            }

            $values = array();
            $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type'));
            foreach ($propertyNode->childNodes as $valueNode) {
                switch ($type) {
                    case PropertyType::NAME:
                    case PropertyType::URI:
                    case PropertyType::WEAKREFERENCE:
                    case PropertyType::REFERENCE:
                    case PropertyType::PATH:
                    case PropertyType::DECIMAL:
                    case PropertyType::STRING:
                        $values[] = $valueNode->nodeValue;
                        break;
                    case PropertyType::BOOLEAN:
                        $values[] = (bool) $valueNode->nodeValue;
                        break;
                    case PropertyType::LONG:
                        $values[] = (int) $valueNode->nodeValue;
                        break;
                    case PropertyType::BINARY:
                        $values[] = (int) $valueNode->nodeValue;
                        break;
                    case PropertyType::DATE:
                        $date = $valueNode->nodeValue;
                        if ($date) {
                            $date = new \DateTime($date);
                            $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
                            // Jackalope expects a string, might make sense to refactor to allow \DateTime instances too
                            $date = $valueConverter->convertType($date, PropertyType::STRING);
                        }
                        $values[] = $date;
                        break;
                    case PropertyType::DOUBLE:
                        $values[] = (double) $valueNode->nodeValue;
                        break;
                    default:
                        throw new \InvalidArgumentException("Type with constant $type not found.");
                }
            }

            switch ($type) {
                case PropertyType::BINARY:
                    $data->{':' . $name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0];
                    break;
                default:
                    $data->{$name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0];
                    $data->{':' . $name} = $type;
                    break;
            }
        }

        return $data;
    }
Exemplo n.º 9
0
 /**
  * {@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")));
     }
 }
Exemplo n.º 10
0
 /**
  * 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;
     }
 }
 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;
 }
Exemplo n.º 12
0
 /**
  * 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;
 }
Exemplo n.º 13
0
 /**
  * Map a property from the given property XML node to the given \stdClass node representation
  *
  * @param \DOMElement $propertyNode
  * @param \stdClass $data
  */
 protected function mapPropertyFromElement(\DOMElement $propertyNode, \stdClass $data)
 {
     $name = $propertyNode->getAttribute('sv:name');
     $values = array();
     $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type'));
     foreach ($propertyNode->childNodes as $valueNode) {
         switch ($type) {
             case PropertyType::NAME:
             case PropertyType::URI:
             case PropertyType::WEAKREFERENCE:
             case PropertyType::REFERENCE:
             case PropertyType::PATH:
             case PropertyType::DECIMAL:
             case PropertyType::STRING:
                 $values[] = $valueNode->nodeValue;
                 break;
             case PropertyType::BOOLEAN:
                 $values[] = (bool) $valueNode->nodeValue;
                 break;
             case PropertyType::LONG:
                 $values[] = (int) $valueNode->nodeValue;
                 break;
             case PropertyType::BINARY:
                 $values[] = (int) $valueNode->nodeValue;
                 break;
             case PropertyType::DATE:
                 $date = $valueNode->nodeValue;
                 if ($date) {
                     $date = new \DateTime($date);
                     $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
                     // Jackalope expects a string, might make sense to refactor to allow \DateTime instances too
                     $date = $this->valueConverter->convertType($date, PropertyType::STRING);
                 }
                 $values[] = $date;
                 break;
             case PropertyType::DOUBLE:
                 $values[] = (double) $valueNode->nodeValue;
                 break;
             default:
                 throw new \InvalidArgumentException("Type with constant {$type} not found.");
         }
     }
     switch ($type) {
         case PropertyType::BINARY:
             $data->{':' . $name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0];
             break;
         default:
             $data->{$name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0];
             $data->{':' . $name} = $type;
             break;
     }
 }
Exemplo n.º 14
0
 /**
  * @expectedException \InvalidArgumentException
  */
 public function testValueFromNameInvalid()
 {
     PropertyType::valueFromName('Notexisting');
 }
Exemplo n.º 15
0
 /**
  * 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));
 }
Exemplo n.º 16
0
 /**
  * Get the value of a dcr:value node in the right format specified by the
  * dcr type.
  *
  * This uses PropertyType but takes into account the special case that
  * boolean false is encoded as string "false" which is otherwise true in php.
  *
  * <dcr:value dcr:type="Boolean">false</dcr:value>
  *
  * @param DOMElement $node      a dcr:value xml element
  * @param string     $attribute the attribute name
  *
  * @return mixed the node value converted to the specified type.
  */
 private function getDcrValue(DOMElement $node)
 {
     $type = $node->getAttribute('dcr:type');
     if (PropertyType::TYPENAME_BOOLEAN == $type && 'false' == $node->nodeValue) {
         return false;
     }
     return $this->valueConverter->convertType($node->nodeValue, PropertyType::valueFromName($type));
 }
Exemplo n.º 17
0
 /**
  * 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));
             }
         }
     }
 }
Exemplo n.º 18
0
 /**
  * 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);
             }
         }
     }
 }
Exemplo n.º 19
0
 /**
  * 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>
 }