public function copyNode($srcAbsPath, $dstAbsPath, $srcWorkspace = null)
{
$this->assertLoggedIn();
if (null !== $srcWorkspace && !$this->workspaceExists($srcWorkspace)) {
throw new NoSuchWorkspaceException("Source workspace '{$srcWorkspace}' does not exist.");
}
$srcWorkspace = $srcWorkspace ?: $this->workspaceName;
PathHelper::assertValidAbsolutePath($dstAbsPath, true, true, $this->getNamespacePrefixes());
$srcNodeId = $this->getSystemIdForNode($srcAbsPath, $srcWorkspace);
if (!$srcNodeId) {
throw new PathNotFoundException("Source path '{$srcAbsPath}' not found");
}
if ($this->getSystemIdForNode($dstAbsPath)) {
throw new ItemExistsException("Cannot copy to destination path '{$dstAbsPath}' that already exists.");
}
if (!$this->getSystemIdForNode(PathHelper::getParentPath($dstAbsPath))) {
throw new PathNotFoundException("Parent of the destination path '" . $dstAbsPath . "' has to exist.");
}
// Algorithm:
// 1. Select all nodes with path $srcAbsPath."%" and iterate them
// 2. create a new node with path $dstAbsPath + leftovers, with a new uuid. Save old => new uuid
// 3. copy all properties from old node to new node
// 4. if a reference is in the properties, either update the uuid based on the map if its inside the copied graph or keep it.
// 5. "May drop mixin types"
$query = 'SELECT * FROM phpcr_nodes WHERE (path = ? OR path LIKE ?) AND workspace_name = ?';
$stmt = $this->getConnection()->executeQuery($query, array($srcAbsPath, $srcAbsPath . '/%', $srcWorkspace));
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$uuidMap = array();
$resultSetUuids = array();
// first iterate and build up an array of all the UUIDs in the result set
foreach ($rows as $row) {
$resultSetUuids[$row['identifier']] = $row['path'];
}
// array of references to remap within the copied tree
$referenceElsToRemap = array();
// array references that will need updating in the database
$referencesToUpdate = array();
foreach ($rows as $row) {
$newPath = str_replace($srcAbsPath, $dstAbsPath, $row['path']);
$stringDom = new \DOMDocument('1.0', 'UTF-8');
$stringDom->loadXML($row['props']);
$numericalDom = null;
if ($row['numerical_props']) {
$numericalDom = new \DOMDocument('1.0', 'UTF-8');
$numericalDom->loadXML($row['numerical_props']);
}
$propsData = array('stringDom' => $stringDom, 'numericalDom' => $numericalDom, 'references' => array());
$xpath = new \DOMXpath($stringDom);
$referenceEls = $xpath->query('.//sv:property[@sv:type="reference" or @sv:type="Reference" or @sv:type="weakreference" or @sv:type="WeakReference"]');
$references = array();
foreach ($referenceEls as $referenceEl) {
$propName = $referenceEl->getAttribute('sv:name');
$values = array();
foreach ($xpath->query('./sv:value', $referenceEl) as $valueEl) {
$values[] = $valueEl->nodeValue;
}
$references[$propName] = array('type' => PropertyType::valueFromName($referenceEl->getAttribute('sv:type')), 'values' => $values);
if (isset($resultSetUuids[$referenceEl->nodeValue])) {
$referenceElsToRemap[] = array($referenceEl, $newPath, $row['type'], $propsData);
}
}
$originalUuid = $row['identifier'];
// when copying a node, the copy is always a new node. set $isNewNode to true
$newNodeId = $this->syncNode(null, $newPath, $row['type'], true, array(), $propsData);
if ($references) {
$referencesToUpdate[$newNodeId] = array('path' => $row['path'], 'properties' => $references);
}
$newUuid = $this->nodeIdentifiers[$newPath];
$uuidMap[$originalUuid] = $newUuid;
$query = 'INSERT INTO phpcr_binarydata (node_id, property_name, workspace_name, idx, data)' . ' SELECT ?, b.property_name, ?, b.idx, b.data FROM phpcr_binarydata b WHERE b.node_id = ?';
try {
$this->getConnection()->executeUpdate($query, array($newNodeId, $this->workspaceName, $row['id']));
} catch (DBALException $e) {
throw new RepositoryException("Unexpected exception while copying node from {$srcAbsPath} to {$dstAbsPath}", $e->getCode(), $e);
}
}
foreach ($referenceElsToRemap as $data) {
list($referenceEl, $newPath, $type, $propsData) = $data;
$referenceEl->nodeValue = $uuidMap[$referenceEl->nodeValue];
$this->syncNode($this->nodeIdentifiers[$newPath], $newPath, $type, false, array(), $propsData);
}
$this->syncReferences($referencesToUpdate);
}