xref: /plugin/davcal/vendor/sabre/dav/lib/DAV/Tree.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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