rootNode = $rootNode; } /** * Returns the INode object for the requested path * * @param string $path * @return INode */ function getNodeForPath($path) { $path = trim($path, '/'); if (isset($this->cache[$path])) return $this->cache[$path]; // Is it the root node? if (!strlen($path)) { return $this->rootNode; } // Attempting to fetch its parent list($parentName, $baseName) = URLUtil::splitPath($path); // If there was no parent, we must simply ask it from the root node. if ($parentName === "") { $node = $this->rootNode->getChild($baseName); } else { // Otherwise, we recursively grab the parent and ask him/her. $parent = $this->getNodeForPath($parentName); if (!($parent instanceof ICollection)) throw new Exception\NotFound('Could not find node at path: ' . $path); $node = $parent->getChild($baseName); } $this->cache[$path] = $node; return $node; } /** * This function allows you to check if a node exists. * * Implementors of this class should override this method to make * it cheaper. * * @param string $path * @return bool */ function nodeExists($path) { try { // The root always exists if ($path === '') return true; list($parent, $base) = URLUtil::splitPath($path); $parentNode = $this->getNodeForPath($parent); if (!$parentNode instanceof ICollection) return false; return $parentNode->childExists($base); } catch (Exception\NotFound $e) { return false; } } /** * Copies a file from path to another * * @param string $sourcePath The source location * @param string $destinationPath The full destination path * @return void */ function copy($sourcePath, $destinationPath) { $sourceNode = $this->getNodeForPath($sourcePath); // grab the dirname and basename components list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); $destinationParent = $this->getNodeForPath($destinationDir); $this->copyNode($sourceNode, $destinationParent, $destinationName); $this->markDirty($destinationDir); } /** * Moves a file from one location to another * * @param string $sourcePath The path to the file which should be moved * @param string $destinationPath The full destination path, so not just the destination parent node * @return int */ function move($sourcePath, $destinationPath) { list($sourceDir) = URLUtil::splitPath($sourcePath); list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); if ($sourceDir === $destinationDir) { // If this is a 'local' rename, it means we can just trigger a rename. $sourceNode = $this->getNodeForPath($sourcePath); $sourceNode->setName($destinationName); } else { $newParentNode = $this->getNodeForPath($destinationDir); $moveSuccess = false; if ($newParentNode instanceof IMoveTarget) { // The target collection may be able to handle the move $sourceNode = $this->getNodeForPath($sourcePath); $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode); } if (!$moveSuccess) { $this->copy($sourcePath, $destinationPath); $this->getNodeForPath($sourcePath)->delete(); } } $this->markDirty($sourceDir); $this->markDirty($destinationDir); } /** * Deletes a node from the tree * * @param string $path * @return void */ function delete($path) { $node = $this->getNodeForPath($path); $node->delete(); list($parent) = URLUtil::splitPath($path); $this->markDirty($parent); } /** * Returns a list of childnodes for a given path. * * @param string $path * @return array */ function getChildren($path) { $node = $this->getNodeForPath($path); $children = $node->getChildren(); $basePath = trim($path, '/') . '/'; foreach ($children as $child) { $this->cache[$basePath . $child->getName()] = $child; } return $children; } /** * This method is called with every tree update * * Examples of tree updates are: * * node deletions * * node creations * * copy * * move * * renaming nodes * * If Tree classes implement a form of caching, this will allow * them to make sure caches will be expired. * * If a path is passed, it is assumed that the entire subtree is dirty * * @param string $path * @return void */ function markDirty($path) { // We don't care enough about sub-paths // flushing the entire cache $path = trim($path, '/'); foreach ($this->cache as $nodePath => $node) { if ($nodePath == $path || strpos($nodePath, $path . '/') === 0) unset($this->cache[$nodePath]); } } /** * This method tells the tree system to pre-fetch and cache a list of * children of a single parent. * * There are a bunch of operations in the WebDAV stack that request many * children (based on uris), and sometimes fetching many at once can * optimize this. * * This method returns an array with the found nodes. It's keys are the * original paths. The result may be out of order. * * @param array $paths List of nodes that must be fetched. * @return array */ function getMultipleNodes($paths) { // Finding common parents $parents = []; foreach ($paths as $path) { list($parent, $node) = URLUtil::splitPath($path); if (!isset($parents[$parent])) { $parents[$parent] = [$node]; } else { $parents[$parent][] = $node; } } $result = []; foreach ($parents as $parent => $children) { $parentNode = $this->getNodeForPath($parent); if ($parentNode instanceof IMultiGet) { foreach ($parentNode->getMultipleChildren($children) as $childNode) { $fullPath = $parent . '/' . $childNode->getName(); $result[$fullPath] = $childNode; $this->cache[$fullPath] = $childNode; } } else { foreach ($children as $child) { $fullPath = $parent . '/' . $child; $result[$fullPath] = $this->getNodeForPath($fullPath); } } } return $result; } /** * copyNode * * @param INode $source * @param ICollection $destinationParent * @param string $destinationName * @return void */ protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null) { if (!$destinationName) $destinationName = $source->getName(); if ($source instanceof IFile) { $data = $source->get(); // If the body was a string, we need to convert it to a stream if (is_string($data)) { $stream = fopen('php://temp', 'r+'); fwrite($stream, $data); rewind($stream); $data = $stream; } $destinationParent->createFile($destinationName, $data); $destination = $destinationParent->getChild($destinationName); } elseif ($source instanceof ICollection) { $destinationParent->createDirectory($destinationName); $destination = $destinationParent->getChild($destinationName); foreach ($source->getChildren() as $child) { $this->copyNode($child, $destination); } } if ($source instanceof IProperties && $destination instanceof IProperties) { $props = $source->getProperties([]); $propPatch = new PropPatch($props); $destination->propPatch($propPatch); $propPatch->commit(); } } }