Tree.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\DAV;
  4. use Sabre\Uri;
  5. /**
  6. * The tree object is responsible for basic tree operations.
  7. *
  8. * It allows for fetching nodes by path, facilitates deleting, copying and
  9. * moving.
  10. *
  11. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  12. * @author Evert Pot (http://evertpot.com/)
  13. * @license http://sabre.io/license/ Modified BSD License
  14. */
  15. class Tree implements INodeByPath
  16. {
  17. /**
  18. * The root node.
  19. *
  20. * @var ICollection
  21. */
  22. protected $rootNode;
  23. /**
  24. * This is the node cache. Accessed nodes are stored here.
  25. * Arrays keys are path names, values are the actual nodes.
  26. *
  27. * @var array
  28. */
  29. protected $cache = [];
  30. /**
  31. * Creates the object.
  32. *
  33. * This method expects the rootObject to be passed as a parameter
  34. */
  35. public function __construct(ICollection $rootNode)
  36. {
  37. $this->rootNode = $rootNode;
  38. }
  39. /**
  40. * Returns the INode object for the requested path.
  41. *
  42. * @param string $path
  43. *
  44. * @return INode
  45. */
  46. public function getNodeForPath($path)
  47. {
  48. $path = trim($path, '/');
  49. if (isset($this->cache[$path])) {
  50. return $this->cache[$path];
  51. }
  52. // Is it the root node?
  53. if (!strlen($path)) {
  54. return $this->rootNode;
  55. }
  56. $parts = explode('/', $path);
  57. $node = $this->rootNode;
  58. while (count($parts)) {
  59. if (!($node instanceof ICollection)) {
  60. throw new Exception\NotFound('Could not find node at path: '.$path);
  61. }
  62. if ($node instanceof INodeByPath) {
  63. $targetNode = $node->getNodeForPath(implode('/', $parts));
  64. if ($targetNode instanceof Node) {
  65. $node = $targetNode;
  66. break;
  67. }
  68. }
  69. $part = array_shift($parts);
  70. if ('' !== $part) {
  71. $node = $node->getChild($part);
  72. }
  73. }
  74. $this->cache[$path] = $node;
  75. return $node;
  76. }
  77. /**
  78. * This function allows you to check if a node exists.
  79. *
  80. * Implementors of this class should override this method to make
  81. * it cheaper.
  82. *
  83. * @param string $path
  84. *
  85. * @return bool
  86. */
  87. public function nodeExists($path)
  88. {
  89. try {
  90. // The root always exists
  91. if ('' === $path) {
  92. return true;
  93. }
  94. list($parent, $base) = Uri\split($path);
  95. $parentNode = $this->getNodeForPath($parent);
  96. if (!$parentNode instanceof ICollection) {
  97. return false;
  98. }
  99. return $parentNode->childExists($base);
  100. } catch (Exception\NotFound $e) {
  101. return false;
  102. }
  103. }
  104. /**
  105. * Copies a file from path to another.
  106. *
  107. * @param string $sourcePath The source location
  108. * @param string $destinationPath The full destination path
  109. */
  110. public function copy($sourcePath, $destinationPath)
  111. {
  112. $sourceNode = $this->getNodeForPath($sourcePath);
  113. // grab the dirname and basename components
  114. list($destinationDir, $destinationName) = Uri\split($destinationPath);
  115. $destinationParent = $this->getNodeForPath($destinationDir);
  116. // Check if the target can handle the copy itself. If not, we do it ourselves.
  117. if (!$destinationParent instanceof ICopyTarget || !$destinationParent->copyInto($destinationName, $sourcePath, $sourceNode)) {
  118. $this->copyNode($sourceNode, $destinationParent, $destinationName);
  119. }
  120. $this->markDirty($destinationDir);
  121. }
  122. /**
  123. * Moves a file from one location to another.
  124. *
  125. * @param string $sourcePath The path to the file which should be moved
  126. * @param string $destinationPath The full destination path, so not just the destination parent node
  127. */
  128. public function move($sourcePath, $destinationPath)
  129. {
  130. list($sourceDir) = Uri\split($sourcePath);
  131. list($destinationDir, $destinationName) = Uri\split($destinationPath);
  132. if ($sourceDir === $destinationDir) {
  133. // If this is a 'local' rename, it means we can just trigger a rename.
  134. $sourceNode = $this->getNodeForPath($sourcePath);
  135. $sourceNode->setName($destinationName);
  136. } else {
  137. $newParentNode = $this->getNodeForPath($destinationDir);
  138. $moveSuccess = false;
  139. if ($newParentNode instanceof IMoveTarget) {
  140. // The target collection may be able to handle the move
  141. $sourceNode = $this->getNodeForPath($sourcePath);
  142. $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode);
  143. }
  144. if (!$moveSuccess) {
  145. $this->copy($sourcePath, $destinationPath);
  146. $this->getNodeForPath($sourcePath)->delete();
  147. }
  148. }
  149. $this->markDirty($sourceDir);
  150. $this->markDirty($destinationDir);
  151. }
  152. /**
  153. * Deletes a node from the tree.
  154. *
  155. * @param string $path
  156. */
  157. public function delete($path)
  158. {
  159. $node = $this->getNodeForPath($path);
  160. $node->delete();
  161. list($parent) = Uri\split($path);
  162. $this->markDirty($parent);
  163. }
  164. /**
  165. * Returns a list of childnodes for a given path.
  166. *
  167. * @param string $path
  168. *
  169. * @return \Traversable
  170. */
  171. public function getChildren($path)
  172. {
  173. $node = $this->getNodeForPath($path);
  174. $basePath = trim($path, '/');
  175. if ('' !== $basePath) {
  176. $basePath .= '/';
  177. }
  178. foreach ($node->getChildren() as $child) {
  179. $this->cache[$basePath.$child->getName()] = $child;
  180. yield $child;
  181. }
  182. }
  183. /**
  184. * This method is called with every tree update.
  185. *
  186. * Examples of tree updates are:
  187. * * node deletions
  188. * * node creations
  189. * * copy
  190. * * move
  191. * * renaming nodes
  192. *
  193. * If Tree classes implement a form of caching, this will allow
  194. * them to make sure caches will be expired.
  195. *
  196. * If a path is passed, it is assumed that the entire subtree is dirty
  197. *
  198. * @param string $path
  199. */
  200. public function markDirty($path)
  201. {
  202. // We don't care enough about sub-paths
  203. // flushing the entire cache
  204. $path = trim($path, '/');
  205. foreach ($this->cache as $nodePath => $node) {
  206. if ('' === $path || $nodePath == $path || 0 === strpos((string) $nodePath, $path.'/')) {
  207. unset($this->cache[$nodePath]);
  208. }
  209. }
  210. }
  211. /**
  212. * This method tells the tree system to pre-fetch and cache a list of
  213. * children of a single parent.
  214. *
  215. * There are a bunch of operations in the WebDAV stack that request many
  216. * children (based on uris), and sometimes fetching many at once can
  217. * optimize this.
  218. *
  219. * This method returns an array with the found nodes. It's keys are the
  220. * original paths. The result may be out of order.
  221. *
  222. * @param array $paths list of nodes that must be fetched
  223. *
  224. * @return array
  225. */
  226. public function getMultipleNodes($paths)
  227. {
  228. // Finding common parents
  229. $parents = [];
  230. foreach ($paths as $path) {
  231. list($parent, $node) = Uri\split($path);
  232. if (!isset($parents[$parent])) {
  233. $parents[$parent] = [$node];
  234. } else {
  235. $parents[$parent][] = $node;
  236. }
  237. }
  238. $result = [];
  239. foreach ($parents as $parent => $children) {
  240. $parentNode = $this->getNodeForPath($parent);
  241. if ($parentNode instanceof IMultiGet) {
  242. foreach ($parentNode->getMultipleChildren($children) as $childNode) {
  243. $fullPath = $parent.'/'.$childNode->getName();
  244. $result[$fullPath] = $childNode;
  245. $this->cache[$fullPath] = $childNode;
  246. }
  247. } else {
  248. foreach ($children as $child) {
  249. $fullPath = $parent.'/'.$child;
  250. $result[$fullPath] = $this->getNodeForPath($fullPath);
  251. }
  252. }
  253. }
  254. return $result;
  255. }
  256. /**
  257. * copyNode.
  258. *
  259. * @param string $destinationName
  260. */
  261. protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null)
  262. {
  263. if ('' === (string) $destinationName) {
  264. $destinationName = $source->getName();
  265. }
  266. $destination = null;
  267. if ($source instanceof IFile) {
  268. $data = $source->get();
  269. // If the body was a string, we need to convert it to a stream
  270. if (is_string($data)) {
  271. $stream = fopen('php://temp', 'r+');
  272. fwrite($stream, $data);
  273. rewind($stream);
  274. $data = $stream;
  275. }
  276. $destinationParent->createFile($destinationName, $data);
  277. $destination = $destinationParent->getChild($destinationName);
  278. } elseif ($source instanceof ICollection) {
  279. $destinationParent->createDirectory($destinationName);
  280. $destination = $destinationParent->getChild($destinationName);
  281. foreach ($source->getChildren() as $child) {
  282. $this->copyNode($child, $destination);
  283. }
  284. }
  285. if ($source instanceof IProperties && $destination instanceof IProperties) {
  286. $props = $source->getProperties([]);
  287. $propPatch = new PropPatch($props);
  288. $destination->propPatch($propPatch);
  289. $propPatch->commit();
  290. }
  291. }
  292. }