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