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;
}
}