Passed
Push — master ( 1fd674...707cb6 )
by Jan-Christoph
15:26 queued 10s
created
apps/dav/lib/Connector/Sabre/Exception/InvalidPath.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -61,17 +61,17 @@
 block discarded – undo
61 61
 	 * @param \DOMElement $errorNode
62 62
 	 * @return void
63 63
 	 */
64
-	public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
64
+	public function serialize(\Sabre\DAV\Server $server, \DOMElement $errorNode) {
65 65
 
66 66
 		// set ownCloud namespace
67 67
 		$errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
68 68
 
69 69
 		// adding the retry node
70
-		$error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true));
70
+		$error = $errorNode->ownerDocument->createElementNS('o:', 'o:retry', var_export($this->retry, true));
71 71
 		$errorNode->appendChild($error);
72 72
 
73 73
 		// adding the message node
74
-		$error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage());
74
+		$error = $errorNode->ownerDocument->createElementNS('o:', 'o:reason', $this->getMessage());
75 75
 		$errorNode->appendChild($error);
76 76
 	}
77 77
 
Please login to merge, or discard this patch.
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -26,54 +26,54 @@
 block discarded – undo
26 26
 
27 27
 class InvalidPath extends Exception {
28 28
 
29
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
29
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
30 30
 
31
-	/**
32
-	 * @var bool
33
-	 */
34
-	private $retry;
31
+    /**
32
+     * @var bool
33
+     */
34
+    private $retry;
35 35
 
36
-	/**
37
-	 * @param string $message
38
-	 * @param bool $retry
39
-	 * @param \Exception|null $previous
40
-	 */
41
-	public function __construct($message, $retry = false, \Exception $previous = null) {
42
-		parent::__construct($message, 0, $previous);
43
-		$this->retry = $retry;
44
-	}
36
+    /**
37
+     * @param string $message
38
+     * @param bool $retry
39
+     * @param \Exception|null $previous
40
+     */
41
+    public function __construct($message, $retry = false, \Exception $previous = null) {
42
+        parent::__construct($message, 0, $previous);
43
+        $this->retry = $retry;
44
+    }
45 45
 
46
-	/**
47
-	 * Returns the HTTP status code for this exception
48
-	 *
49
-	 * @return int
50
-	 */
51
-	public function getHTTPCode() {
46
+    /**
47
+     * Returns the HTTP status code for this exception
48
+     *
49
+     * @return int
50
+     */
51
+    public function getHTTPCode() {
52 52
 
53
-		return 400;
53
+        return 400;
54 54
 
55
-	}
55
+    }
56 56
 
57
-	/**
58
-	 * This method allows the exception to include additional information
59
-	 * into the WebDAV error response
60
-	 *
61
-	 * @param \Sabre\DAV\Server $server
62
-	 * @param \DOMElement $errorNode
63
-	 * @return void
64
-	 */
65
-	public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
57
+    /**
58
+     * This method allows the exception to include additional information
59
+     * into the WebDAV error response
60
+     *
61
+     * @param \Sabre\DAV\Server $server
62
+     * @param \DOMElement $errorNode
63
+     * @return void
64
+     */
65
+    public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
66 66
 
67
-		// set ownCloud namespace
68
-		$errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
67
+        // set ownCloud namespace
68
+        $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
69 69
 
70
-		// adding the retry node
71
-		$error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true));
72
-		$errorNode->appendChild($error);
70
+        // adding the retry node
71
+        $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true));
72
+        $errorNode->appendChild($error);
73 73
 
74
-		// adding the message node
75
-		$error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage());
76
-		$errorNode->appendChild($error);
77
-	}
74
+        // adding the message node
75
+        $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage());
76
+        $errorNode->appendChild($error);
77
+    }
78 78
 
79 79
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Exception/PasswordLoginForbidden.php 1 patch
Indentation   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -29,27 +29,27 @@
 block discarded – undo
29 29
 
30 30
 class PasswordLoginForbidden extends NotAuthenticated {
31 31
 
32
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
33
-
34
-	public function getHTTPCode() {
35
-		return 401;
36
-	}
37
-
38
-	/**
39
-	 * This method allows the exception to include additional information
40
-	 * into the WebDAV error response
41
-	 *
42
-	 * @param Server $server
43
-	 * @param DOMElement $errorNode
44
-	 * @return void
45
-	 */
46
-	public function serialize(Server $server, DOMElement $errorNode) {
47
-
48
-		// set ownCloud namespace
49
-		$errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
50
-
51
-		$error = $errorNode->ownerDocument->createElementNS('o:', 'o:hint', 'password login forbidden');
52
-		$errorNode->appendChild($error);
53
-	}
32
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
33
+
34
+    public function getHTTPCode() {
35
+        return 401;
36
+    }
37
+
38
+    /**
39
+     * This method allows the exception to include additional information
40
+     * into the WebDAV error response
41
+     *
42
+     * @param Server $server
43
+     * @param DOMElement $errorNode
44
+     * @return void
45
+     */
46
+    public function serialize(Server $server, DOMElement $errorNode) {
47
+
48
+        // set ownCloud namespace
49
+        $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
50
+
51
+        $error = $errorNode->ownerDocument->createElementNS('o:', 'o:hint', 'password login forbidden');
52
+        $errorNode->appendChild($error);
53
+    }
54 54
 
55 55
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Exception/FileLocked.php 2 patches
Indentation   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -30,20 +30,20 @@
 block discarded – undo
30 30
 
31 31
 class FileLocked extends \Sabre\DAV\Exception {
32 32
 
33
-	public function __construct($message = "", $code = 0, Exception $previous = null) {
34
-		if($previous instanceof \OCP\Files\LockNotAcquiredException) {
35
-			$message = sprintf('Target file %s is locked by another process.', $previous->path);
36
-		}
37
-		parent::__construct($message, $code, $previous);
38
-	}
33
+    public function __construct($message = "", $code = 0, Exception $previous = null) {
34
+        if($previous instanceof \OCP\Files\LockNotAcquiredException) {
35
+            $message = sprintf('Target file %s is locked by another process.', $previous->path);
36
+        }
37
+        parent::__construct($message, $code, $previous);
38
+    }
39 39
 
40
-	/**
41
-	 * Returns the HTTP status code for this exception
42
-	 *
43
-	 * @return int
44
-	 */
45
-	public function getHTTPCode() {
40
+    /**
41
+     * Returns the HTTP status code for this exception
42
+     *
43
+     * @return int
44
+     */
45
+    public function getHTTPCode() {
46 46
 
47
-		return 423;
48
-	}
47
+        return 423;
48
+    }
49 49
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -31,7 +31,7 @@
 block discarded – undo
31 31
 class FileLocked extends \Sabre\DAV\Exception {
32 32
 
33 33
 	public function __construct($message = "", $code = 0, Exception $previous = null) {
34
-		if($previous instanceof \OCP\Files\LockNotAcquiredException) {
34
+		if ($previous instanceof \OCP\Files\LockNotAcquiredException) {
35 35
 			$message = sprintf('Target file %s is locked by another process.', $previous->path);
36 36
 		}
37 37
 		parent::__construct($message, $code, $previous);
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Exception/UnsupportedMediaType.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -31,15 +31,15 @@
 block discarded – undo
31 31
  */
32 32
 class UnsupportedMediaType extends \Sabre\DAV\Exception {
33 33
 
34
-	/**
35
-	 * Returns the HTTP status code for this exception
36
-	 *
37
-	 * @return int
38
-	 */
39
-	public function getHTTPCode() {
34
+    /**
35
+     * Returns the HTTP status code for this exception
36
+     *
37
+     * @return int
38
+     */
39
+    public function getHTTPCode() {
40 40
 
41
-		return 415;
41
+        return 415;
42 42
 
43
-	}
43
+    }
44 44
 
45 45
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Exception/EntityTooLarge.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -31,15 +31,15 @@
 block discarded – undo
31 31
  */
32 32
 class EntityTooLarge extends \Sabre\DAV\Exception {
33 33
 
34
-	/**
35
-	 * Returns the HTTP status code for this exception
36
-	 *
37
-	 * @return int
38
-	 */
39
-	public function getHTTPCode() {
34
+    /**
35
+     * Returns the HTTP status code for this exception
36
+     *
37
+     * @return int
38
+     */
39
+    public function getHTTPCode() {
40 40
 
41
-		return 413;
41
+        return 413;
42 42
 
43
-	}
43
+    }
44 44
 
45 45
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/CustomPropertiesBackend.php 2 patches
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 			// we catch the exception to prevent breaking the whole list with a 404
112 112
 			// (soft fail)
113 113
 			\OC::$server->getLogger()->warning(
114
-				'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
114
+				'Could not get node for path: \"'.$path.'\" : '.$e->getMessage(),
115 115
 				array('app' => 'files')
116 116
 			);
117 117
 			return;
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 		$statement = $this->connection->prepare(
171 171
 			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
172 172
 		);
173
-		$statement->execute(array($this->user, '/' . $path));
173
+		$statement->execute(array($this->user, '/'.$path));
174 174
 		$statement->closeCursor();
175 175
 
176 176
 		unset($this->cache[$path]);
@@ -186,10 +186,10 @@  discard block
 block discarded – undo
186 186
 	 */
187 187
 	public function move($source, $destination) {
188 188
 		$statement = $this->connection->prepare(
189
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
189
+			'UPDATE `*PREFIX*properties` SET `propertypath` = ?'.
190 190
 			' WHERE `userid` = ? AND `propertypath` = ?'
191 191
 		);
192
-		$statement->execute(array('/' . $destination, $this->user, '/' . $source));
192
+		$statement->execute(array('/'.$destination, $this->user, '/'.$source));
193 193
 		$statement->closeCursor();
194 194
 	}
195 195
 
@@ -250,13 +250,13 @@  discard block
 block discarded – undo
250 250
 	private function updateProperties($node, $properties) {
251 251
 		$path = $node->getPath();
252 252
 
253
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
253
+		$deleteStatement = 'DELETE FROM `*PREFIX*properties`'.
254 254
 			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
255 255
 
256
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
256
+		$insertStatement = 'INSERT INTO `*PREFIX*properties`'.
257 257
 			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
258 258
 
259
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
259
+		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?'.
260 260
 			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
261 261
 
262 262
 		// TODO: use "insert or update" strategy ?
@@ -329,7 +329,7 @@  discard block
 block discarded – undo
329 329
 
330 330
 		$result = $this->connection->executeQuery(
331 331
 			$sql,
332
-			array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
332
+			array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')).'/%', $requestedProperties),
333 333
 			array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
334 334
 		);
335 335
 
Please login to merge, or discard this patch.
Indentation   +317 added lines, -317 removed lines patch added patch discarded remove patch
@@ -36,322 +36,322 @@
 block discarded – undo
36 36
 
37 37
 class CustomPropertiesBackend implements BackendInterface {
38 38
 
39
-	/**
40
-	 * Ignored properties
41
-	 *
42
-	 * @var array
43
-	 */
44
-	private $ignoredProperties = array(
45
-		'{DAV:}getcontentlength',
46
-		'{DAV:}getcontenttype',
47
-		'{DAV:}getetag',
48
-		'{DAV:}quota-used-bytes',
49
-		'{DAV:}quota-available-bytes',
50
-		'{http://owncloud.org/ns}permissions',
51
-		'{http://owncloud.org/ns}downloadURL',
52
-		'{http://owncloud.org/ns}dDC',
53
-		'{http://owncloud.org/ns}size',
54
-		'{http://nextcloud.org/ns}is-encrypted',
55
-	);
56
-
57
-	/**
58
-	 * @var Tree
59
-	 */
60
-	private $tree;
61
-
62
-	/**
63
-	 * @var IDBConnection
64
-	 */
65
-	private $connection;
66
-
67
-	/**
68
-	 * @var IUser
69
-	 */
70
-	private $user;
71
-
72
-	/**
73
-	 * Properties cache
74
-	 *
75
-	 * @var array
76
-	 */
77
-	private $cache = [];
78
-
79
-	/**
80
-	 * @param Tree $tree node tree
81
-	 * @param IDBConnection $connection database connection
82
-	 * @param IUser $user owner of the tree and properties
83
-	 */
84
-	public function __construct(
85
-		Tree $tree,
86
-		IDBConnection $connection,
87
-		IUser $user) {
88
-		$this->tree = $tree;
89
-		$this->connection = $connection;
90
-		$this->user = $user->getUID();
91
-	}
92
-
93
-	/**
94
-	 * Fetches properties for a path.
95
-	 *
96
-	 * @param string $path
97
-	 * @param PropFind $propFind
98
-	 * @return void
99
-	 */
100
-	public function propFind($path, PropFind $propFind) {
101
-		try {
102
-			$node = $this->tree->getNodeForPath($path);
103
-			if (!($node instanceof Node)) {
104
-				return;
105
-			}
106
-		} catch (ServiceUnavailable $e) {
107
-			// might happen for unavailable mount points, skip
108
-			return;
109
-		} catch (NotFound $e) {
110
-			// in some rare (buggy) cases the node might not be found,
111
-			// we catch the exception to prevent breaking the whole list with a 404
112
-			// (soft fail)
113
-			\OC::$server->getLogger()->warning(
114
-				'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
115
-				array('app' => 'files')
116
-			);
117
-			return;
118
-		}
119
-
120
-		$requestedProps = $propFind->get404Properties();
121
-
122
-		// these might appear
123
-		$requestedProps = array_diff(
124
-			$requestedProps,
125
-			$this->ignoredProperties
126
-		);
127
-
128
-		if (empty($requestedProps)) {
129
-			return;
130
-		}
131
-
132
-		if ($node instanceof Directory
133
-			&& $propFind->getDepth() !== 0
134
-		) {
135
-			// note: pre-fetching only supported for depth <= 1
136
-			$this->loadChildrenProperties($node, $requestedProps);
137
-		}
138
-
139
-		$props = $this->getProperties($node, $requestedProps);
140
-		foreach ($props as $propName => $propValue) {
141
-			$propFind->set($propName, $propValue);
142
-		}
143
-	}
144
-
145
-	/**
146
-	 * Updates properties for a path
147
-	 *
148
-	 * @param string $path
149
-	 * @param PropPatch $propPatch
150
-	 *
151
-	 * @return void
152
-	 */
153
-	public function propPatch($path, PropPatch $propPatch) {
154
-		$node = $this->tree->getNodeForPath($path);
155
-		if (!($node instanceof Node)) {
156
-			return;
157
-		}
158
-
159
-		$propPatch->handleRemaining(function($changedProps) use ($node) {
160
-			return $this->updateProperties($node, $changedProps);
161
-		});
162
-	}
163
-
164
-	/**
165
-	 * This method is called after a node is deleted.
166
-	 *
167
-	 * @param string $path path of node for which to delete properties
168
-	 */
169
-	public function delete($path) {
170
-		$statement = $this->connection->prepare(
171
-			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
172
-		);
173
-		$statement->execute(array($this->user, '/' . $path));
174
-		$statement->closeCursor();
175
-
176
-		unset($this->cache[$path]);
177
-	}
178
-
179
-	/**
180
-	 * This method is called after a successful MOVE
181
-	 *
182
-	 * @param string $source
183
-	 * @param string $destination
184
-	 *
185
-	 * @return void
186
-	 */
187
-	public function move($source, $destination) {
188
-		$statement = $this->connection->prepare(
189
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
190
-			' WHERE `userid` = ? AND `propertypath` = ?'
191
-		);
192
-		$statement->execute(array('/' . $destination, $this->user, '/' . $source));
193
-		$statement->closeCursor();
194
-	}
195
-
196
-	/**
197
-	 * Returns a list of properties for this nodes.;
198
-	 * @param Node $node
199
-	 * @param array $requestedProperties requested properties or empty array for "all"
200
-	 * @return array
201
-	 * @note The properties list is a list of propertynames the client
202
-	 * requested, encoded as xmlnamespace#tagName, for example:
203
-	 * http://www.example.org/namespace#author If the array is empty, all
204
-	 * properties should be returned
205
-	 */
206
-	private function getProperties(Node $node, array $requestedProperties) {
207
-		$path = $node->getPath();
208
-		if (isset($this->cache[$path])) {
209
-			return $this->cache[$path];
210
-		}
211
-
212
-		// TODO: chunking if more than 1000 properties
213
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
214
-
215
-		$whereValues = array($this->user, $path);
216
-		$whereTypes = array(null, null);
217
-
218
-		if (!empty($requestedProperties)) {
219
-			// request only a subset
220
-			$sql .= ' AND `propertyname` in (?)';
221
-			$whereValues[] = $requestedProperties;
222
-			$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
223
-		}
224
-
225
-		$result = $this->connection->executeQuery(
226
-			$sql,
227
-			$whereValues,
228
-			$whereTypes
229
-		);
230
-
231
-		$props = [];
232
-		while ($row = $result->fetch()) {
233
-			$props[$row['propertyname']] = $row['propertyvalue'];
234
-		}
235
-
236
-		$result->closeCursor();
237
-
238
-		$this->cache[$path] = $props;
239
-		return $props;
240
-	}
241
-
242
-	/**
243
-	 * Update properties
244
-	 *
245
-	 * @param Node $node node for which to update properties
246
-	 * @param array $properties array of properties to update
247
-	 *
248
-	 * @return bool
249
-	 */
250
-	private function updateProperties($node, $properties) {
251
-		$path = $node->getPath();
252
-
253
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
254
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
255
-
256
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
257
-			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
258
-
259
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
260
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
261
-
262
-		// TODO: use "insert or update" strategy ?
263
-		$existing = $this->getProperties($node, array());
264
-		$this->connection->beginTransaction();
265
-		foreach ($properties as $propertyName => $propertyValue) {
266
-			// If it was null, we need to delete the property
267
-			if (is_null($propertyValue)) {
268
-				if (array_key_exists($propertyName, $existing)) {
269
-					$this->connection->executeUpdate($deleteStatement,
270
-						array(
271
-							$this->user,
272
-							$path,
273
-							$propertyName
274
-						)
275
-					);
276
-				}
277
-			} else {
278
-				if (!array_key_exists($propertyName, $existing)) {
279
-					$this->connection->executeUpdate($insertStatement,
280
-						array(
281
-							$this->user,
282
-							$path,
283
-							$propertyName,
284
-							$propertyValue
285
-						)
286
-					);
287
-				} else {
288
-					$this->connection->executeUpdate($updateStatement,
289
-						array(
290
-							$propertyValue,
291
-							$this->user,
292
-							$path,
293
-							$propertyName
294
-						)
295
-					);
296
-				}
297
-			}
298
-		}
299
-
300
-		$this->connection->commit();
301
-		unset($this->cache[$path]);
302
-
303
-		return true;
304
-	}
305
-
306
-	/**
307
-	 * Bulk load properties for directory children
308
-	 *
309
-	 * @param Directory $node
310
-	 * @param array $requestedProperties requested properties
311
-	 *
312
-	 * @return void
313
-	 */
314
-	private function loadChildrenProperties(Directory $node, $requestedProperties) {
315
-		$path = $node->getPath();
316
-		if (isset($this->cache[$path])) {
317
-			// we already loaded them at some point
318
-			return;
319
-		}
320
-
321
-		$childNodes = $node->getChildren();
322
-		// pre-fill cache
323
-		foreach ($childNodes as $childNode) {
324
-			$this->cache[$childNode->getPath()] = [];
325
-		}
326
-
327
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
328
-		$sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
329
-
330
-		$result = $this->connection->executeQuery(
331
-			$sql,
332
-			array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
333
-			array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
334
-		);
335
-
336
-		$oldPath = null;
337
-		$props = [];
338
-		while ($row = $result->fetch()) {
339
-			$path = $row['propertypath'];
340
-			if ($oldPath !== $path) {
341
-				// save previously gathered props
342
-				$this->cache[$oldPath] = $props;
343
-				$oldPath = $path;
344
-				// prepare props for next path
345
-				$props = [];
346
-			}
347
-			$props[$row['propertyname']] = $row['propertyvalue'];
348
-		}
349
-		if (!is_null($oldPath)) {
350
-			// save props from last run
351
-			$this->cache[$oldPath] = $props;
352
-		}
353
-
354
-		$result->closeCursor();
355
-	}
39
+    /**
40
+     * Ignored properties
41
+     *
42
+     * @var array
43
+     */
44
+    private $ignoredProperties = array(
45
+        '{DAV:}getcontentlength',
46
+        '{DAV:}getcontenttype',
47
+        '{DAV:}getetag',
48
+        '{DAV:}quota-used-bytes',
49
+        '{DAV:}quota-available-bytes',
50
+        '{http://owncloud.org/ns}permissions',
51
+        '{http://owncloud.org/ns}downloadURL',
52
+        '{http://owncloud.org/ns}dDC',
53
+        '{http://owncloud.org/ns}size',
54
+        '{http://nextcloud.org/ns}is-encrypted',
55
+    );
56
+
57
+    /**
58
+     * @var Tree
59
+     */
60
+    private $tree;
61
+
62
+    /**
63
+     * @var IDBConnection
64
+     */
65
+    private $connection;
66
+
67
+    /**
68
+     * @var IUser
69
+     */
70
+    private $user;
71
+
72
+    /**
73
+     * Properties cache
74
+     *
75
+     * @var array
76
+     */
77
+    private $cache = [];
78
+
79
+    /**
80
+     * @param Tree $tree node tree
81
+     * @param IDBConnection $connection database connection
82
+     * @param IUser $user owner of the tree and properties
83
+     */
84
+    public function __construct(
85
+        Tree $tree,
86
+        IDBConnection $connection,
87
+        IUser $user) {
88
+        $this->tree = $tree;
89
+        $this->connection = $connection;
90
+        $this->user = $user->getUID();
91
+    }
92
+
93
+    /**
94
+     * Fetches properties for a path.
95
+     *
96
+     * @param string $path
97
+     * @param PropFind $propFind
98
+     * @return void
99
+     */
100
+    public function propFind($path, PropFind $propFind) {
101
+        try {
102
+            $node = $this->tree->getNodeForPath($path);
103
+            if (!($node instanceof Node)) {
104
+                return;
105
+            }
106
+        } catch (ServiceUnavailable $e) {
107
+            // might happen for unavailable mount points, skip
108
+            return;
109
+        } catch (NotFound $e) {
110
+            // in some rare (buggy) cases the node might not be found,
111
+            // we catch the exception to prevent breaking the whole list with a 404
112
+            // (soft fail)
113
+            \OC::$server->getLogger()->warning(
114
+                'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
115
+                array('app' => 'files')
116
+            );
117
+            return;
118
+        }
119
+
120
+        $requestedProps = $propFind->get404Properties();
121
+
122
+        // these might appear
123
+        $requestedProps = array_diff(
124
+            $requestedProps,
125
+            $this->ignoredProperties
126
+        );
127
+
128
+        if (empty($requestedProps)) {
129
+            return;
130
+        }
131
+
132
+        if ($node instanceof Directory
133
+            && $propFind->getDepth() !== 0
134
+        ) {
135
+            // note: pre-fetching only supported for depth <= 1
136
+            $this->loadChildrenProperties($node, $requestedProps);
137
+        }
138
+
139
+        $props = $this->getProperties($node, $requestedProps);
140
+        foreach ($props as $propName => $propValue) {
141
+            $propFind->set($propName, $propValue);
142
+        }
143
+    }
144
+
145
+    /**
146
+     * Updates properties for a path
147
+     *
148
+     * @param string $path
149
+     * @param PropPatch $propPatch
150
+     *
151
+     * @return void
152
+     */
153
+    public function propPatch($path, PropPatch $propPatch) {
154
+        $node = $this->tree->getNodeForPath($path);
155
+        if (!($node instanceof Node)) {
156
+            return;
157
+        }
158
+
159
+        $propPatch->handleRemaining(function($changedProps) use ($node) {
160
+            return $this->updateProperties($node, $changedProps);
161
+        });
162
+    }
163
+
164
+    /**
165
+     * This method is called after a node is deleted.
166
+     *
167
+     * @param string $path path of node for which to delete properties
168
+     */
169
+    public function delete($path) {
170
+        $statement = $this->connection->prepare(
171
+            'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
172
+        );
173
+        $statement->execute(array($this->user, '/' . $path));
174
+        $statement->closeCursor();
175
+
176
+        unset($this->cache[$path]);
177
+    }
178
+
179
+    /**
180
+     * This method is called after a successful MOVE
181
+     *
182
+     * @param string $source
183
+     * @param string $destination
184
+     *
185
+     * @return void
186
+     */
187
+    public function move($source, $destination) {
188
+        $statement = $this->connection->prepare(
189
+            'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
190
+            ' WHERE `userid` = ? AND `propertypath` = ?'
191
+        );
192
+        $statement->execute(array('/' . $destination, $this->user, '/' . $source));
193
+        $statement->closeCursor();
194
+    }
195
+
196
+    /**
197
+     * Returns a list of properties for this nodes.;
198
+     * @param Node $node
199
+     * @param array $requestedProperties requested properties or empty array for "all"
200
+     * @return array
201
+     * @note The properties list is a list of propertynames the client
202
+     * requested, encoded as xmlnamespace#tagName, for example:
203
+     * http://www.example.org/namespace#author If the array is empty, all
204
+     * properties should be returned
205
+     */
206
+    private function getProperties(Node $node, array $requestedProperties) {
207
+        $path = $node->getPath();
208
+        if (isset($this->cache[$path])) {
209
+            return $this->cache[$path];
210
+        }
211
+
212
+        // TODO: chunking if more than 1000 properties
213
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
214
+
215
+        $whereValues = array($this->user, $path);
216
+        $whereTypes = array(null, null);
217
+
218
+        if (!empty($requestedProperties)) {
219
+            // request only a subset
220
+            $sql .= ' AND `propertyname` in (?)';
221
+            $whereValues[] = $requestedProperties;
222
+            $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
223
+        }
224
+
225
+        $result = $this->connection->executeQuery(
226
+            $sql,
227
+            $whereValues,
228
+            $whereTypes
229
+        );
230
+
231
+        $props = [];
232
+        while ($row = $result->fetch()) {
233
+            $props[$row['propertyname']] = $row['propertyvalue'];
234
+        }
235
+
236
+        $result->closeCursor();
237
+
238
+        $this->cache[$path] = $props;
239
+        return $props;
240
+    }
241
+
242
+    /**
243
+     * Update properties
244
+     *
245
+     * @param Node $node node for which to update properties
246
+     * @param array $properties array of properties to update
247
+     *
248
+     * @return bool
249
+     */
250
+    private function updateProperties($node, $properties) {
251
+        $path = $node->getPath();
252
+
253
+        $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
254
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
255
+
256
+        $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
257
+            ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
258
+
259
+        $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
260
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
261
+
262
+        // TODO: use "insert or update" strategy ?
263
+        $existing = $this->getProperties($node, array());
264
+        $this->connection->beginTransaction();
265
+        foreach ($properties as $propertyName => $propertyValue) {
266
+            // If it was null, we need to delete the property
267
+            if (is_null($propertyValue)) {
268
+                if (array_key_exists($propertyName, $existing)) {
269
+                    $this->connection->executeUpdate($deleteStatement,
270
+                        array(
271
+                            $this->user,
272
+                            $path,
273
+                            $propertyName
274
+                        )
275
+                    );
276
+                }
277
+            } else {
278
+                if (!array_key_exists($propertyName, $existing)) {
279
+                    $this->connection->executeUpdate($insertStatement,
280
+                        array(
281
+                            $this->user,
282
+                            $path,
283
+                            $propertyName,
284
+                            $propertyValue
285
+                        )
286
+                    );
287
+                } else {
288
+                    $this->connection->executeUpdate($updateStatement,
289
+                        array(
290
+                            $propertyValue,
291
+                            $this->user,
292
+                            $path,
293
+                            $propertyName
294
+                        )
295
+                    );
296
+                }
297
+            }
298
+        }
299
+
300
+        $this->connection->commit();
301
+        unset($this->cache[$path]);
302
+
303
+        return true;
304
+    }
305
+
306
+    /**
307
+     * Bulk load properties for directory children
308
+     *
309
+     * @param Directory $node
310
+     * @param array $requestedProperties requested properties
311
+     *
312
+     * @return void
313
+     */
314
+    private function loadChildrenProperties(Directory $node, $requestedProperties) {
315
+        $path = $node->getPath();
316
+        if (isset($this->cache[$path])) {
317
+            // we already loaded them at some point
318
+            return;
319
+        }
320
+
321
+        $childNodes = $node->getChildren();
322
+        // pre-fill cache
323
+        foreach ($childNodes as $childNode) {
324
+            $this->cache[$childNode->getPath()] = [];
325
+        }
326
+
327
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
328
+        $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
329
+
330
+        $result = $this->connection->executeQuery(
331
+            $sql,
332
+            array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
333
+            array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
334
+        );
335
+
336
+        $oldPath = null;
337
+        $props = [];
338
+        while ($row = $result->fetch()) {
339
+            $path = $row['propertypath'];
340
+            if ($oldPath !== $path) {
341
+                // save previously gathered props
342
+                $this->cache[$oldPath] = $props;
343
+                $oldPath = $path;
344
+                // prepare props for next path
345
+                $props = [];
346
+            }
347
+            $props[$row['propertyname']] = $row['propertyvalue'];
348
+        }
349
+        if (!is_null($oldPath)) {
350
+            // save props from last run
351
+            $this->cache[$oldPath] = $props;
352
+        }
353
+
354
+        $result->closeCursor();
355
+    }
356 356
 
357 357
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/TagList.php 2 patches
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -34,92 +34,92 @@
 block discarded – undo
34 34
  * This property contains multiple "tag" elements, each containing a tag name.
35 35
  */
36 36
 class TagList implements Element {
37
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
37
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
38 38
 
39
-	/**
40
-	 * tags
41
-	 *
42
-	 * @var array
43
-	 */
44
-	private $tags;
39
+    /**
40
+     * tags
41
+     *
42
+     * @var array
43
+     */
44
+    private $tags;
45 45
 
46
-	/**
47
-	 * @param array $tags
48
-	 */
49
-	public function __construct(array $tags) {
50
-		$this->tags = $tags;
51
-	}
46
+    /**
47
+     * @param array $tags
48
+     */
49
+    public function __construct(array $tags) {
50
+        $this->tags = $tags;
51
+    }
52 52
 
53
-	/**
54
-	 * Returns the tags
55
-	 *
56
-	 * @return array
57
-	 */
58
-	public function getTags() {
53
+    /**
54
+     * Returns the tags
55
+     *
56
+     * @return array
57
+     */
58
+    public function getTags() {
59 59
 
60
-		return $this->tags;
60
+        return $this->tags;
61 61
 
62
-	}
62
+    }
63 63
 
64
-	/**
65
-	 * The deserialize method is called during xml parsing.
66
-	 *
67
-	 * This method is called statictly, this is because in theory this method
68
-	 * may be used as a type of constructor, or factory method.
69
-	 *
70
-	 * Often you want to return an instance of the current class, but you are
71
-	 * free to return other data as well.
72
-	 *
73
-	 * You are responsible for advancing the reader to the next element. Not
74
-	 * doing anything will result in a never-ending loop.
75
-	 *
76
-	 * If you just want to skip parsing for this element altogether, you can
77
-	 * just call $reader->next();
78
-	 *
79
-	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
80
-	 * the next element.
81
-	 *
82
-	 * @param Reader $reader
83
-	 * @return mixed
84
-	 */
85
-	static function xmlDeserialize(Reader $reader) {
86
-		$tags = [];
64
+    /**
65
+     * The deserialize method is called during xml parsing.
66
+     *
67
+     * This method is called statictly, this is because in theory this method
68
+     * may be used as a type of constructor, or factory method.
69
+     *
70
+     * Often you want to return an instance of the current class, but you are
71
+     * free to return other data as well.
72
+     *
73
+     * You are responsible for advancing the reader to the next element. Not
74
+     * doing anything will result in a never-ending loop.
75
+     *
76
+     * If you just want to skip parsing for this element altogether, you can
77
+     * just call $reader->next();
78
+     *
79
+     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
80
+     * the next element.
81
+     *
82
+     * @param Reader $reader
83
+     * @return mixed
84
+     */
85
+    static function xmlDeserialize(Reader $reader) {
86
+        $tags = [];
87 87
 
88
-		$tree = $reader->parseInnerTree();
89
-		if ($tree === null) {
90
-			return null;
91
-		}
92
-		foreach ($tree as $elem) {
93
-			if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') {
94
-				$tags[] = $elem['value'];
95
-			}
96
-		}
97
-		return new self($tags);
98
-	}
88
+        $tree = $reader->parseInnerTree();
89
+        if ($tree === null) {
90
+            return null;
91
+        }
92
+        foreach ($tree as $elem) {
93
+            if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') {
94
+                $tags[] = $elem['value'];
95
+            }
96
+        }
97
+        return new self($tags);
98
+    }
99 99
 
100
-	/**
101
-	 * The xmlSerialize metod is called during xml writing.
102
-	 *
103
-	 * Use the $writer argument to write its own xml serialization.
104
-	 *
105
-	 * An important note: do _not_ create a parent element. Any element
106
-	 * implementing XmlSerializble should only ever write what's considered
107
-	 * its 'inner xml'.
108
-	 *
109
-	 * The parent of the current element is responsible for writing a
110
-	 * containing element.
111
-	 *
112
-	 * This allows serializers to be re-used for different element names.
113
-	 *
114
-	 * If you are opening new elements, you must also close them again.
115
-	 *
116
-	 * @param Writer $writer
117
-	 * @return void
118
-	 */
119
-	function xmlSerialize(Writer $writer) {
100
+    /**
101
+     * The xmlSerialize metod is called during xml writing.
102
+     *
103
+     * Use the $writer argument to write its own xml serialization.
104
+     *
105
+     * An important note: do _not_ create a parent element. Any element
106
+     * implementing XmlSerializble should only ever write what's considered
107
+     * its 'inner xml'.
108
+     *
109
+     * The parent of the current element is responsible for writing a
110
+     * containing element.
111
+     *
112
+     * This allows serializers to be re-used for different element names.
113
+     *
114
+     * If you are opening new elements, you must also close them again.
115
+     *
116
+     * @param Writer $writer
117
+     * @return void
118
+     */
119
+    function xmlSerialize(Writer $writer) {
120 120
 
121
-		foreach ($this->tags as $tag) {
122
-			$writer->writeElement('{' . self::NS_OWNCLOUD . '}tag', $tag);
123
-		}
124
-	}
121
+        foreach ($this->tags as $tag) {
122
+            $writer->writeElement('{' . self::NS_OWNCLOUD . '}tag', $tag);
123
+        }
124
+    }
125 125
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 			return null;
91 91
 		}
92 92
 		foreach ($tree as $elem) {
93
-			if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') {
93
+			if ($elem['name'] === '{'.self::NS_OWNCLOUD.'}tag') {
94 94
 				$tags[] = $elem['value'];
95 95
 			}
96 96
 		}
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
 	function xmlSerialize(Writer $writer) {
120 120
 
121 121
 		foreach ($this->tags as $tag) {
122
-			$writer->writeElement('{' . self::NS_OWNCLOUD . '}tag', $tag);
122
+			$writer->writeElement('{'.self::NS_OWNCLOUD.'}tag', $tag);
123 123
 		}
124 124
 	}
125 125
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/SharesPlugin.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
 
149 149
 		$shareTypesByFileId = [];
150 150
 
151
-		foreach($shares as $fileId => $sharesForFile) {
151
+		foreach ($shares as $fileId => $sharesForFile) {
152 152
 			$types = array_map(function(IShare $share) {
153 153
 				return $share->getShareType();
154 154
 			}, $sharesForFile);
@@ -189,7 +189,7 @@  discard block
 block discarded – undo
189 189
 			}
190 190
 		}
191 191
 
192
-		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
192
+		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function() use ($sabreNode) {
193 193
 			if (isset($this->cachedShareTypes[$sabreNode->getId()])) {
194 194
 				$shareTypes = $this->cachedShareTypes[$sabreNode->getId()];
195 195
 			} else {
Please login to merge, or discard this patch.
Indentation   +178 added lines, -178 removed lines patch added patch discarded remove patch
@@ -34,182 +34,182 @@
 block discarded – undo
34 34
  */
35 35
 class SharesPlugin extends \Sabre\DAV\ServerPlugin {
36 36
 
37
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
38
-	const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
39
-
40
-	/**
41
-	 * Reference to main server object
42
-	 *
43
-	 * @var \Sabre\DAV\Server
44
-	 */
45
-	private $server;
46
-
47
-	/**
48
-	 * @var \OCP\Share\IManager
49
-	 */
50
-	private $shareManager;
51
-
52
-	/**
53
-	 * @var \Sabre\DAV\Tree
54
-	 */
55
-	private $tree;
56
-
57
-	/**
58
-	 * @var string
59
-	 */
60
-	private $userId;
61
-
62
-	/**
63
-	 * @var \OCP\Files\Folder
64
-	 */
65
-	private $userFolder;
66
-
67
-	/**
68
-	 * @var IShare[]
69
-	 */
70
-	private $cachedShareTypes;
71
-
72
-	private $cachedFolders = [];
73
-
74
-	/**
75
-	 * @param \Sabre\DAV\Tree $tree tree
76
-	 * @param IUserSession $userSession user session
77
-	 * @param \OCP\Files\Folder $userFolder user home folder
78
-	 * @param \OCP\Share\IManager $shareManager share manager
79
-	 */
80
-	public function __construct(
81
-		\Sabre\DAV\Tree $tree,
82
-		IUserSession $userSession,
83
-		\OCP\Files\Folder $userFolder,
84
-		\OCP\Share\IManager $shareManager
85
-	) {
86
-		$this->tree = $tree;
87
-		$this->shareManager = $shareManager;
88
-		$this->userFolder = $userFolder;
89
-		$this->userId = $userSession->getUser()->getUID();
90
-		$this->cachedShareTypes = [];
91
-	}
92
-
93
-	/**
94
-	 * This initializes the plugin.
95
-	 *
96
-	 * This function is called by \Sabre\DAV\Server, after
97
-	 * addPlugin is called.
98
-	 *
99
-	 * This method should set up the required event subscriptions.
100
-	 *
101
-	 * @param \Sabre\DAV\Server $server
102
-	 */
103
-	public function initialize(\Sabre\DAV\Server $server) {
104
-		$server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
105
-		$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
106
-		$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
107
-
108
-		$this->server = $server;
109
-		$this->server->on('propFind', array($this, 'handleGetProperties'));
110
-	}
111
-
112
-	/**
113
-	 * Return a list of share types for outgoing shares
114
-	 *
115
-	 * @param \OCP\Files\Node $node file node
116
-	 *
117
-	 * @return int[] array of share types
118
-	 */
119
-	private function getShareTypes(\OCP\Files\Node $node) {
120
-		$shareTypes = [];
121
-		$requestedShareTypes = [
122
-			\OCP\Share::SHARE_TYPE_USER,
123
-			\OCP\Share::SHARE_TYPE_GROUP,
124
-			\OCP\Share::SHARE_TYPE_LINK,
125
-			\OCP\Share::SHARE_TYPE_REMOTE,
126
-			\OCP\Share::SHARE_TYPE_EMAIL,
127
-			\OCP\Share::SHARE_TYPE_ROOM,
128
-		];
129
-		foreach ($requestedShareTypes as $requestedShareType) {
130
-			// one of each type is enough to find out about the types
131
-			$shares = $this->shareManager->getSharesBy(
132
-				$this->userId,
133
-				$requestedShareType,
134
-				$node,
135
-				false,
136
-				1
137
-			);
138
-			if (!empty($shares)) {
139
-				$shareTypes[] = $requestedShareType;
140
-			}
141
-		}
142
-		return $shareTypes;
143
-	}
144
-
145
-	private function getSharesTypesInFolder(\OCP\Files\Folder $node) {
146
-		$shares = $this->shareManager->getSharesInFolder(
147
-			$this->userId,
148
-			$node,
149
-			true
150
-		);
151
-
152
-		$shareTypesByFileId = [];
153
-
154
-		foreach($shares as $fileId => $sharesForFile) {
155
-			$types = array_map(function(IShare $share) {
156
-				return $share->getShareType();
157
-			}, $sharesForFile);
158
-			$types = array_unique($types);
159
-			sort($types);
160
-			$shareTypesByFileId[$fileId] = $types;
161
-		}
162
-
163
-		return $shareTypesByFileId;
164
-	}
165
-
166
-	/**
167
-	 * Adds shares to propfind response
168
-	 *
169
-	 * @param PropFind $propFind propfind object
170
-	 * @param \Sabre\DAV\INode $sabreNode sabre node
171
-	 */
172
-	public function handleGetProperties(
173
-		PropFind $propFind,
174
-		\Sabre\DAV\INode $sabreNode
175
-	) {
176
-		if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
177
-			return;
178
-		}
179
-
180
-		// need prefetch ?
181
-		if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
182
-			&& $propFind->getDepth() !== 0
183
-			&& !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME))
184
-		) {
185
-			$folderNode = $this->userFolder->get($sabreNode->getPath());
186
-
187
-			$childShares = $this->getSharesTypesInFolder($folderNode);
188
-			$this->cachedFolders[] = $sabreNode->getPath();
189
-			$this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode);
190
-			foreach ($childShares as $id => $shares) {
191
-				$this->cachedShareTypes[$id] = $shares;
192
-			}
193
-		}
194
-
195
-		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
196
-			if (isset($this->cachedShareTypes[$sabreNode->getId()])) {
197
-				$shareTypes = $this->cachedShareTypes[$sabreNode->getId()];
198
-			} else {
199
-				list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
200
-				if ($parentPath === '') {
201
-					$parentPath = '/';
202
-				}
203
-				// if we already cached the folder this file is in we know there are no shares for this file
204
-				if (array_search($parentPath, $this->cachedFolders) === false) {
205
-					$node = $this->userFolder->get($sabreNode->getPath());
206
-					$shareTypes = $this->getShareTypes($node);
207
-				} else {
208
-					return [];
209
-				}
210
-			}
211
-
212
-			return new ShareTypeList($shareTypes);
213
-		});
214
-	}
37
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
38
+    const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
39
+
40
+    /**
41
+     * Reference to main server object
42
+     *
43
+     * @var \Sabre\DAV\Server
44
+     */
45
+    private $server;
46
+
47
+    /**
48
+     * @var \OCP\Share\IManager
49
+     */
50
+    private $shareManager;
51
+
52
+    /**
53
+     * @var \Sabre\DAV\Tree
54
+     */
55
+    private $tree;
56
+
57
+    /**
58
+     * @var string
59
+     */
60
+    private $userId;
61
+
62
+    /**
63
+     * @var \OCP\Files\Folder
64
+     */
65
+    private $userFolder;
66
+
67
+    /**
68
+     * @var IShare[]
69
+     */
70
+    private $cachedShareTypes;
71
+
72
+    private $cachedFolders = [];
73
+
74
+    /**
75
+     * @param \Sabre\DAV\Tree $tree tree
76
+     * @param IUserSession $userSession user session
77
+     * @param \OCP\Files\Folder $userFolder user home folder
78
+     * @param \OCP\Share\IManager $shareManager share manager
79
+     */
80
+    public function __construct(
81
+        \Sabre\DAV\Tree $tree,
82
+        IUserSession $userSession,
83
+        \OCP\Files\Folder $userFolder,
84
+        \OCP\Share\IManager $shareManager
85
+    ) {
86
+        $this->tree = $tree;
87
+        $this->shareManager = $shareManager;
88
+        $this->userFolder = $userFolder;
89
+        $this->userId = $userSession->getUser()->getUID();
90
+        $this->cachedShareTypes = [];
91
+    }
92
+
93
+    /**
94
+     * This initializes the plugin.
95
+     *
96
+     * This function is called by \Sabre\DAV\Server, after
97
+     * addPlugin is called.
98
+     *
99
+     * This method should set up the required event subscriptions.
100
+     *
101
+     * @param \Sabre\DAV\Server $server
102
+     */
103
+    public function initialize(\Sabre\DAV\Server $server) {
104
+        $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
105
+        $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
106
+        $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
107
+
108
+        $this->server = $server;
109
+        $this->server->on('propFind', array($this, 'handleGetProperties'));
110
+    }
111
+
112
+    /**
113
+     * Return a list of share types for outgoing shares
114
+     *
115
+     * @param \OCP\Files\Node $node file node
116
+     *
117
+     * @return int[] array of share types
118
+     */
119
+    private function getShareTypes(\OCP\Files\Node $node) {
120
+        $shareTypes = [];
121
+        $requestedShareTypes = [
122
+            \OCP\Share::SHARE_TYPE_USER,
123
+            \OCP\Share::SHARE_TYPE_GROUP,
124
+            \OCP\Share::SHARE_TYPE_LINK,
125
+            \OCP\Share::SHARE_TYPE_REMOTE,
126
+            \OCP\Share::SHARE_TYPE_EMAIL,
127
+            \OCP\Share::SHARE_TYPE_ROOM,
128
+        ];
129
+        foreach ($requestedShareTypes as $requestedShareType) {
130
+            // one of each type is enough to find out about the types
131
+            $shares = $this->shareManager->getSharesBy(
132
+                $this->userId,
133
+                $requestedShareType,
134
+                $node,
135
+                false,
136
+                1
137
+            );
138
+            if (!empty($shares)) {
139
+                $shareTypes[] = $requestedShareType;
140
+            }
141
+        }
142
+        return $shareTypes;
143
+    }
144
+
145
+    private function getSharesTypesInFolder(\OCP\Files\Folder $node) {
146
+        $shares = $this->shareManager->getSharesInFolder(
147
+            $this->userId,
148
+            $node,
149
+            true
150
+        );
151
+
152
+        $shareTypesByFileId = [];
153
+
154
+        foreach($shares as $fileId => $sharesForFile) {
155
+            $types = array_map(function(IShare $share) {
156
+                return $share->getShareType();
157
+            }, $sharesForFile);
158
+            $types = array_unique($types);
159
+            sort($types);
160
+            $shareTypesByFileId[$fileId] = $types;
161
+        }
162
+
163
+        return $shareTypesByFileId;
164
+    }
165
+
166
+    /**
167
+     * Adds shares to propfind response
168
+     *
169
+     * @param PropFind $propFind propfind object
170
+     * @param \Sabre\DAV\INode $sabreNode sabre node
171
+     */
172
+    public function handleGetProperties(
173
+        PropFind $propFind,
174
+        \Sabre\DAV\INode $sabreNode
175
+    ) {
176
+        if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
177
+            return;
178
+        }
179
+
180
+        // need prefetch ?
181
+        if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
182
+            && $propFind->getDepth() !== 0
183
+            && !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME))
184
+        ) {
185
+            $folderNode = $this->userFolder->get($sabreNode->getPath());
186
+
187
+            $childShares = $this->getSharesTypesInFolder($folderNode);
188
+            $this->cachedFolders[] = $sabreNode->getPath();
189
+            $this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode);
190
+            foreach ($childShares as $id => $shares) {
191
+                $this->cachedShareTypes[$id] = $shares;
192
+            }
193
+        }
194
+
195
+        $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
196
+            if (isset($this->cachedShareTypes[$sabreNode->getId()])) {
197
+                $shareTypes = $this->cachedShareTypes[$sabreNode->getId()];
198
+            } else {
199
+                list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
200
+                if ($parentPath === '') {
201
+                    $parentPath = '/';
202
+                }
203
+                // if we already cached the folder this file is in we know there are no shares for this file
204
+                if (array_search($parentPath, $this->cachedFolders) === false) {
205
+                    $node = $this->userFolder->get($sabreNode->getPath());
206
+                    $shareTypes = $this->getShareTypes($node);
207
+                } else {
208
+                    return [];
209
+                }
210
+            }
211
+
212
+            return new ShareTypeList($shareTypes);
213
+        });
214
+    }
215 215
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/FilesPlugin.php 3 patches
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -245,7 +245,9 @@
 block discarded – undo
245 245
 	function httpGet(RequestInterface $request, ResponseInterface $response) {
246 246
 		// Only handle valid files
247 247
 		$node = $this->tree->getNodeForPath($request->getPath());
248
-		if (!($node instanceof IFile)) return;
248
+		if (!($node instanceof IFile)) {
249
+		    return;
250
+		}
249 251
 
250 252
 		// adds a 'Content-Disposition: attachment' header in case no disposition
251 253
 		// header has been set before
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
 		$this->server->on('propPatch', array($this, 'handleUpdateProperties'));
169 169
 		$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
170 170
 		$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
171
-		$this->server->on('afterMethod:GET', [$this,'httpGet']);
171
+		$this->server->on('afterMethod:GET', [$this, 'httpGet']);
172 172
 		$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
173 173
 		$this->server->on('afterResponse', function($request, ResponseInterface $response) {
174 174
 			$body = $response->getBody();
@@ -198,11 +198,11 @@  discard block
 block discarded – undo
198 198
 		if ($sourceDir !== $destinationDir) {
199 199
 			$sourceNodeFileInfo = $sourceNode->getFileInfo();
200 200
 			if ($sourceNodeFileInfo === null) {
201
-				throw new NotFound($source . ' does not exist');
201
+				throw new NotFound($source.' does not exist');
202 202
  			}
203 203
 
204 204
 			if (!$sourceNodeFileInfo->isDeletable()) {
205
-				throw new Forbidden($source . " cannot be deleted");
205
+				throw new Forbidden($source." cannot be deleted");
206 206
 			}
207 207
 		}
208 208
 	}
@@ -255,10 +255,10 @@  discard block
 block discarded – undo
255 255
 					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
256 256
 					Request::USER_AGENT_FREEBOX,
257 257
 				])) {
258
-				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
258
+				$response->addHeader('Content-Disposition', 'attachment; filename="'.rawurlencode($filename).'"');
259 259
 			} else {
260
-				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
261
-													 . '; filename="' . rawurlencode($filename) . '"');
260
+				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($filename)
261
+													 . '; filename="'.rawurlencode($filename).'"');
262 262
 			}
263 263
 		}
264 264
 
@@ -339,13 +339,13 @@  discard block
 block discarded – undo
339 339
 				}
340 340
 			});
341 341
 
342
-			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
342
+			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function() use ($node) {
343 343
 				return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
344 344
 			});
345 345
 			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
346 346
 				return $node->getSize();
347 347
 			});
348
-			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
348
+			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function() use ($node) {
349 349
 				return $node->getFileInfo()->getMountPoint()->getMountType();
350 350
 			});
351 351
 		}
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
 			list($path, $name) = \Sabre\Uri\split($filePath);
438 438
 			$info = \OC_FileChunking::decodeName($name);
439 439
 			if (!empty($info)) {
440
-				$filePath = $path . '/' . $info['name'];
440
+				$filePath = $path.'/'.$info['name'];
441 441
 			}
442 442
 		}
443 443
 
Please login to merge, or discard this patch.
Indentation   +436 added lines, -436 removed lines patch added patch discarded remove patch
@@ -50,444 +50,444 @@
 block discarded – undo
50 50
 
51 51
 class FilesPlugin extends ServerPlugin {
52 52
 
53
-	// namespace
54
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
55
-	const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
56
-	const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
57
-	const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
58
-	const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
59
-	const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
60
-	const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
61
-	const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
62
-	const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
63
-	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
64
-	const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
65
-	const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
66
-	const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
67
-	const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
68
-	const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
69
-	const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
70
-	const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
71
-	const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
72
-
73
-	/**
74
-	 * Reference to main server object
75
-	 *
76
-	 * @var \Sabre\DAV\Server
77
-	 */
78
-	private $server;
79
-
80
-	/**
81
-	 * @var Tree
82
-	 */
83
-	private $tree;
84
-
85
-	/**
86
-	 * Whether this is public webdav.
87
-	 * If true, some returned information will be stripped off.
88
-	 *
89
-	 * @var bool
90
-	 */
91
-	private $isPublic;
92
-
93
-	/**
94
-	 * @var bool
95
-	 */
96
-	private $downloadAttachment;
97
-
98
-	/**
99
-	 * @var IConfig
100
-	 */
101
-	private $config;
102
-
103
-	/**
104
-	 * @var IRequest
105
-	 */
106
-	private $request;
107
-
108
-	/**
109
-	 * @var IPreview
110
-	 */
111
-	private $previewManager;
112
-
113
-	/**
114
-	 * @param Tree $tree
115
-	 * @param IConfig $config
116
-	 * @param IRequest $request
117
-	 * @param IPreview $previewManager
118
-	 * @param bool $isPublic
119
-	 * @param bool $downloadAttachment
120
-	 */
121
-	public function __construct(Tree $tree,
122
-								IConfig $config,
123
-								IRequest $request,
124
-								IPreview $previewManager,
125
-								$isPublic = false,
126
-								$downloadAttachment = true) {
127
-		$this->tree = $tree;
128
-		$this->config = $config;
129
-		$this->request = $request;
130
-		$this->isPublic = $isPublic;
131
-		$this->downloadAttachment = $downloadAttachment;
132
-		$this->previewManager = $previewManager;
133
-	}
134
-
135
-	/**
136
-	 * This initializes the plugin.
137
-	 *
138
-	 * This function is called by \Sabre\DAV\Server, after
139
-	 * addPlugin is called.
140
-	 *
141
-	 * This method should set up the required event subscriptions.
142
-	 *
143
-	 * @param \Sabre\DAV\Server $server
144
-	 * @return void
145
-	 */
146
-	public function initialize(\Sabre\DAV\Server $server) {
147
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
148
-		$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
149
-		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
150
-		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
151
-		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
152
-		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
153
-		$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
154
-		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
155
-		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
156
-		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
157
-		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
158
-		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
159
-		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
160
-		$server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
161
-		$server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
162
-		$server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME;
163
-
164
-		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
165
-		$allowedProperties = ['{DAV:}getetag'];
166
-		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
167
-
168
-		$this->server = $server;
169
-		$this->server->on('propFind', array($this, 'handleGetProperties'));
170
-		$this->server->on('propPatch', array($this, 'handleUpdateProperties'));
171
-		$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
172
-		$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
173
-		$this->server->on('afterMethod:GET', [$this,'httpGet']);
174
-		$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
175
-		$this->server->on('afterResponse', function($request, ResponseInterface $response) {
176
-			$body = $response->getBody();
177
-			if (is_resource($body)) {
178
-				fclose($body);
179
-			}
180
-		});
181
-		$this->server->on('beforeMove', [$this, 'checkMove']);
182
-	}
183
-
184
-	/**
185
-	 * Plugin that checks if a move can actually be performed.
186
-	 *
187
-	 * @param string $source source path
188
-	 * @param string $destination destination path
189
-	 * @throws Forbidden
190
-	 * @throws NotFound
191
-	 */
192
-	function checkMove($source, $destination) {
193
-		$sourceNode = $this->tree->getNodeForPath($source);
194
-		if (!$sourceNode instanceof Node) {
195
-			return;
196
-		}
197
-		list($sourceDir,) = \Sabre\Uri\split($source);
198
-		list($destinationDir,) = \Sabre\Uri\split($destination);
199
-
200
-		if ($sourceDir !== $destinationDir) {
201
-			$sourceNodeFileInfo = $sourceNode->getFileInfo();
202
-			if ($sourceNodeFileInfo === null) {
203
-				throw new NotFound($source . ' does not exist');
204
- 			}
205
-
206
-			if (!$sourceNodeFileInfo->isDeletable()) {
207
-				throw new Forbidden($source . " cannot be deleted");
208
-			}
209
-		}
210
-	}
211
-
212
-	/**
213
-	 * This sets a cookie to be able to recognize the start of the download
214
-	 * the content must not be longer than 32 characters and must only contain
215
-	 * alphanumeric characters
216
-	 *
217
-	 * @param RequestInterface $request
218
-	 * @param ResponseInterface $response
219
-	 */
220
-	function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
221
-		$queryParams = $request->getQueryParameters();
222
-
223
-		/**
224
-		 * this sets a cookie to be able to recognize the start of the download
225
-		 * the content must not be longer than 32 characters and must only contain
226
-		 * alphanumeric characters
227
-		 */
228
-		if (isset($queryParams['downloadStartSecret'])) {
229
-			$token = $queryParams['downloadStartSecret'];
230
-			if (!isset($token[32])
231
-				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
232
-				// FIXME: use $response->setHeader() instead
233
-				setcookie('ocDownloadStarted', $token, time() + 20, '/');
234
-			}
235
-		}
236
-	}
237
-
238
-	/**
239
-	 * Add headers to file download
240
-	 *
241
-	 * @param RequestInterface $request
242
-	 * @param ResponseInterface $response
243
-	 */
244
-	function httpGet(RequestInterface $request, ResponseInterface $response) {
245
-		// Only handle valid files
246
-		$node = $this->tree->getNodeForPath($request->getPath());
247
-		if (!($node instanceof IFile)) return;
248
-
249
-		// adds a 'Content-Disposition: attachment' header in case no disposition
250
-		// header has been set before
251
-		if ($this->downloadAttachment &&
252
-			$response->getHeader('Content-Disposition') === null) {
253
-			$filename = $node->getName();
254
-			if ($this->request->isUserAgent(
255
-				[
256
-					Request::USER_AGENT_IE,
257
-					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
258
-					Request::USER_AGENT_FREEBOX,
259
-				])) {
260
-				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
261
-			} else {
262
-				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
263
-													 . '; filename="' . rawurlencode($filename) . '"');
264
-			}
265
-		}
266
-
267
-		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
268
-			//Add OC-Checksum header
269
-			/** @var $node File */
270
-			$checksum = $node->getChecksum();
271
-			if ($checksum !== null && $checksum !== '') {
272
-				$response->addHeader('OC-Checksum', $checksum);
273
-			}
274
-		}
275
-	}
276
-
277
-	/**
278
-	 * Adds all ownCloud-specific properties
279
-	 *
280
-	 * @param PropFind $propFind
281
-	 * @param \Sabre\DAV\INode $node
282
-	 * @return void
283
-	 */
284
-	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
285
-
286
-		$httpRequest = $this->server->httpRequest;
287
-
288
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
289
-			/**
290
-			 * This was disabled, because it made dir listing throw an exception,
291
-			 * so users were unable to navigate into folders where one subitem
292
-			 * is blocked by the files_accesscontrol app, see:
293
-			 * https://github.com/nextcloud/files_accesscontrol/issues/65
53
+    // namespace
54
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
55
+    const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
56
+    const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
57
+    const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
58
+    const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
59
+    const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
60
+    const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
61
+    const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
62
+    const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
63
+    const GETETAG_PROPERTYNAME = '{DAV:}getetag';
64
+    const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
65
+    const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
66
+    const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
67
+    const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
68
+    const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
69
+    const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
70
+    const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
71
+    const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
72
+
73
+    /**
74
+     * Reference to main server object
75
+     *
76
+     * @var \Sabre\DAV\Server
77
+     */
78
+    private $server;
79
+
80
+    /**
81
+     * @var Tree
82
+     */
83
+    private $tree;
84
+
85
+    /**
86
+     * Whether this is public webdav.
87
+     * If true, some returned information will be stripped off.
88
+     *
89
+     * @var bool
90
+     */
91
+    private $isPublic;
92
+
93
+    /**
94
+     * @var bool
95
+     */
96
+    private $downloadAttachment;
97
+
98
+    /**
99
+     * @var IConfig
100
+     */
101
+    private $config;
102
+
103
+    /**
104
+     * @var IRequest
105
+     */
106
+    private $request;
107
+
108
+    /**
109
+     * @var IPreview
110
+     */
111
+    private $previewManager;
112
+
113
+    /**
114
+     * @param Tree $tree
115
+     * @param IConfig $config
116
+     * @param IRequest $request
117
+     * @param IPreview $previewManager
118
+     * @param bool $isPublic
119
+     * @param bool $downloadAttachment
120
+     */
121
+    public function __construct(Tree $tree,
122
+                                IConfig $config,
123
+                                IRequest $request,
124
+                                IPreview $previewManager,
125
+                                $isPublic = false,
126
+                                $downloadAttachment = true) {
127
+        $this->tree = $tree;
128
+        $this->config = $config;
129
+        $this->request = $request;
130
+        $this->isPublic = $isPublic;
131
+        $this->downloadAttachment = $downloadAttachment;
132
+        $this->previewManager = $previewManager;
133
+    }
134
+
135
+    /**
136
+     * This initializes the plugin.
137
+     *
138
+     * This function is called by \Sabre\DAV\Server, after
139
+     * addPlugin is called.
140
+     *
141
+     * This method should set up the required event subscriptions.
142
+     *
143
+     * @param \Sabre\DAV\Server $server
144
+     * @return void
145
+     */
146
+    public function initialize(\Sabre\DAV\Server $server) {
147
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
148
+        $server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
149
+        $server->protectedProperties[] = self::FILEID_PROPERTYNAME;
150
+        $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
151
+        $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
152
+        $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
153
+        $server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
154
+        $server->protectedProperties[] = self::SIZE_PROPERTYNAME;
155
+        $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
156
+        $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
157
+        $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
158
+        $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
159
+        $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
160
+        $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
161
+        $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
162
+        $server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME;
163
+
164
+        // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
165
+        $allowedProperties = ['{DAV:}getetag'];
166
+        $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
167
+
168
+        $this->server = $server;
169
+        $this->server->on('propFind', array($this, 'handleGetProperties'));
170
+        $this->server->on('propPatch', array($this, 'handleUpdateProperties'));
171
+        $this->server->on('afterBind', array($this, 'sendFileIdHeader'));
172
+        $this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
173
+        $this->server->on('afterMethod:GET', [$this,'httpGet']);
174
+        $this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
175
+        $this->server->on('afterResponse', function($request, ResponseInterface $response) {
176
+            $body = $response->getBody();
177
+            if (is_resource($body)) {
178
+                fclose($body);
179
+            }
180
+        });
181
+        $this->server->on('beforeMove', [$this, 'checkMove']);
182
+    }
183
+
184
+    /**
185
+     * Plugin that checks if a move can actually be performed.
186
+     *
187
+     * @param string $source source path
188
+     * @param string $destination destination path
189
+     * @throws Forbidden
190
+     * @throws NotFound
191
+     */
192
+    function checkMove($source, $destination) {
193
+        $sourceNode = $this->tree->getNodeForPath($source);
194
+        if (!$sourceNode instanceof Node) {
195
+            return;
196
+        }
197
+        list($sourceDir,) = \Sabre\Uri\split($source);
198
+        list($destinationDir,) = \Sabre\Uri\split($destination);
199
+
200
+        if ($sourceDir !== $destinationDir) {
201
+            $sourceNodeFileInfo = $sourceNode->getFileInfo();
202
+            if ($sourceNodeFileInfo === null) {
203
+                throw new NotFound($source . ' does not exist');
204
+                }
205
+
206
+            if (!$sourceNodeFileInfo->isDeletable()) {
207
+                throw new Forbidden($source . " cannot be deleted");
208
+            }
209
+        }
210
+    }
211
+
212
+    /**
213
+     * This sets a cookie to be able to recognize the start of the download
214
+     * the content must not be longer than 32 characters and must only contain
215
+     * alphanumeric characters
216
+     *
217
+     * @param RequestInterface $request
218
+     * @param ResponseInterface $response
219
+     */
220
+    function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
221
+        $queryParams = $request->getQueryParameters();
222
+
223
+        /**
224
+         * this sets a cookie to be able to recognize the start of the download
225
+         * the content must not be longer than 32 characters and must only contain
226
+         * alphanumeric characters
227
+         */
228
+        if (isset($queryParams['downloadStartSecret'])) {
229
+            $token = $queryParams['downloadStartSecret'];
230
+            if (!isset($token[32])
231
+                && preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
232
+                // FIXME: use $response->setHeader() instead
233
+                setcookie('ocDownloadStarted', $token, time() + 20, '/');
234
+            }
235
+        }
236
+    }
237
+
238
+    /**
239
+     * Add headers to file download
240
+     *
241
+     * @param RequestInterface $request
242
+     * @param ResponseInterface $response
243
+     */
244
+    function httpGet(RequestInterface $request, ResponseInterface $response) {
245
+        // Only handle valid files
246
+        $node = $this->tree->getNodeForPath($request->getPath());
247
+        if (!($node instanceof IFile)) return;
248
+
249
+        // adds a 'Content-Disposition: attachment' header in case no disposition
250
+        // header has been set before
251
+        if ($this->downloadAttachment &&
252
+            $response->getHeader('Content-Disposition') === null) {
253
+            $filename = $node->getName();
254
+            if ($this->request->isUserAgent(
255
+                [
256
+                    Request::USER_AGENT_IE,
257
+                    Request::USER_AGENT_ANDROID_MOBILE_CHROME,
258
+                    Request::USER_AGENT_FREEBOX,
259
+                ])) {
260
+                $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
261
+            } else {
262
+                $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
263
+                                                        . '; filename="' . rawurlencode($filename) . '"');
264
+            }
265
+        }
266
+
267
+        if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
268
+            //Add OC-Checksum header
269
+            /** @var $node File */
270
+            $checksum = $node->getChecksum();
271
+            if ($checksum !== null && $checksum !== '') {
272
+                $response->addHeader('OC-Checksum', $checksum);
273
+            }
274
+        }
275
+    }
276
+
277
+    /**
278
+     * Adds all ownCloud-specific properties
279
+     *
280
+     * @param PropFind $propFind
281
+     * @param \Sabre\DAV\INode $node
282
+     * @return void
283
+     */
284
+    public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
285
+
286
+        $httpRequest = $this->server->httpRequest;
287
+
288
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
289
+            /**
290
+             * This was disabled, because it made dir listing throw an exception,
291
+             * so users were unable to navigate into folders where one subitem
292
+             * is blocked by the files_accesscontrol app, see:
293
+             * https://github.com/nextcloud/files_accesscontrol/issues/65
294 294
 			if (!$node->getFileInfo()->isReadable()) {
295 295
 				// avoid detecting files through this means
296 296
 				throw new NotFound();
297 297
 			}
298
-			 */
299
-
300
-			$propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) {
301
-				return $node->getFileId();
302
-			});
303
-
304
-			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
305
-				return $node->getInternalFileId();
306
-			});
307
-
308
-			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
309
-				$perms = $node->getDavPermissions();
310
-				if ($this->isPublic) {
311
-					// remove mount information
312
-					$perms = str_replace(['S', 'M'], '', $perms);
313
-				}
314
-				return $perms;
315
-			});
316
-
317
-			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
318
-				return $node->getSharePermissions(
319
-					$httpRequest->getRawServerValue('PHP_AUTH_USER')
320
-				);
321
-			});
322
-
323
-			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
324
-				$ncPermissions = $node->getSharePermissions(
325
-					$httpRequest->getRawServerValue('PHP_AUTH_USER')
326
-				);
327
-				$ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
328
-				return json_encode($ocmPermissions);
329
-			});
330
-
331
-			$propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) {
332
-				return $node->getETag();
333
-			});
334
-
335
-			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
336
-				$owner = $node->getOwner();
337
-				if (!$owner) {
338
-					return null;
339
-				} else {
340
-					return $owner->getUID();
341
-				}
342
-			});
343
-			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
344
-				$owner = $node->getOwner();
345
-				if (!$owner) {
346
-					return null;
347
-				} else {
348
-					return $owner->getDisplayName();
349
-				}
350
-			});
351
-
352
-			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
353
-				return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
354
-			});
355
-			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
356
-				return $node->getSize();
357
-			});
358
-			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
359
-				return $node->getFileInfo()->getMountPoint()->getMountType();
360
-			});
361
-		}
362
-
363
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
364
-			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
365
-				return $this->config->getSystemValue('data-fingerprint', '');
366
-			});
367
-		}
368
-
369
-		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
370
-			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) {
371
-				/** @var $node \OCA\DAV\Connector\Sabre\File */
372
-				try {
373
-					$directDownloadUrl = $node->getDirectDownload();
374
-					if (isset($directDownloadUrl['url'])) {
375
-						return $directDownloadUrl['url'];
376
-					}
377
-				} catch (StorageNotAvailableException $e) {
378
-					return false;
379
-				} catch (ForbiddenException $e) {
380
-					return false;
381
-				}
382
-				return false;
383
-			});
384
-
385
-			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
386
-				$checksum = $node->getChecksum();
387
-				if ($checksum === NULL || $checksum === '') {
388
-					return null;
389
-				}
390
-
391
-				return new ChecksumList($checksum);
392
-			});
393
-
394
-		}
395
-
396
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
397
-			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
398
-				return $node->getSize();
399
-			});
400
-
401
-			$propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function() use ($node) {
402
-				return $node->getFileInfo()->isEncrypted() ? '1' : '0';
403
-			});
404
-		}
405
-	}
406
-
407
-	/**
408
-	 * translate Nextcloud permissions to OCM Permissions
409
-	 *
410
-	 * @param $ncPermissions
411
-	 * @return array
412
-	 */
413
-	protected function ncPermissions2ocmPermissions($ncPermissions) {
414
-
415
-		$ocmPermissions = [];
416
-
417
-		if ($ncPermissions & Constants::PERMISSION_SHARE) {
418
-			$ocmPermissions[] = 'share';
419
-		}
420
-
421
-		if ($ncPermissions & Constants::PERMISSION_READ) {
422
-			$ocmPermissions[] = 'read';
423
-		}
424
-
425
-		if (($ncPermissions & Constants::PERMISSION_CREATE) ||
426
-			($ncPermissions & Constants::PERMISSION_UPDATE)) {
427
-			$ocmPermissions[] = 'write';
428
-		}
429
-
430
-		return $ocmPermissions;
431
-
432
-	}
433
-
434
-	/**
435
-	 * Update ownCloud-specific properties
436
-	 *
437
-	 * @param string $path
438
-	 * @param PropPatch $propPatch
439
-	 *
440
-	 * @return void
441
-	 */
442
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
443
-		$node = $this->tree->getNodeForPath($path);
444
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
445
-			return;
446
-		}
447
-
448
-		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) {
449
-			if (empty($time)) {
450
-				return false;
451
-			}
452
-			$node->touch($time);
453
-			return true;
454
-		});
455
-		$propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) {
456
-			if (empty($etag)) {
457
-				return false;
458
-			}
459
-			if ($node->setEtag($etag) !== -1) {
460
-				return true;
461
-			}
462
-			return false;
463
-		});
464
-	}
465
-
466
-	/**
467
-	 * @param string $filePath
468
-	 * @param \Sabre\DAV\INode $node
469
-	 * @throws \Sabre\DAV\Exception\BadRequest
470
-	 */
471
-	public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
472
-		// chunked upload handling
473
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
474
-			list($path, $name) = \Sabre\Uri\split($filePath);
475
-			$info = \OC_FileChunking::decodeName($name);
476
-			if (!empty($info)) {
477
-				$filePath = $path . '/' . $info['name'];
478
-			}
479
-		}
480
-
481
-		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
482
-		if (!$this->server->tree->nodeExists($filePath)) {
483
-			return;
484
-		}
485
-		$node = $this->server->tree->getNodeForPath($filePath);
486
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
487
-			$fileId = $node->getFileId();
488
-			if (!is_null($fileId)) {
489
-				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
490
-			}
491
-		}
492
-	}
298
+             */
299
+
300
+            $propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) {
301
+                return $node->getFileId();
302
+            });
303
+
304
+            $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
305
+                return $node->getInternalFileId();
306
+            });
307
+
308
+            $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
309
+                $perms = $node->getDavPermissions();
310
+                if ($this->isPublic) {
311
+                    // remove mount information
312
+                    $perms = str_replace(['S', 'M'], '', $perms);
313
+                }
314
+                return $perms;
315
+            });
316
+
317
+            $propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
318
+                return $node->getSharePermissions(
319
+                    $httpRequest->getRawServerValue('PHP_AUTH_USER')
320
+                );
321
+            });
322
+
323
+            $propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
324
+                $ncPermissions = $node->getSharePermissions(
325
+                    $httpRequest->getRawServerValue('PHP_AUTH_USER')
326
+                );
327
+                $ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
328
+                return json_encode($ocmPermissions);
329
+            });
330
+
331
+            $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) {
332
+                return $node->getETag();
333
+            });
334
+
335
+            $propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
336
+                $owner = $node->getOwner();
337
+                if (!$owner) {
338
+                    return null;
339
+                } else {
340
+                    return $owner->getUID();
341
+                }
342
+            });
343
+            $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
344
+                $owner = $node->getOwner();
345
+                if (!$owner) {
346
+                    return null;
347
+                } else {
348
+                    return $owner->getDisplayName();
349
+                }
350
+            });
351
+
352
+            $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
353
+                return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
354
+            });
355
+            $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
356
+                return $node->getSize();
357
+            });
358
+            $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
359
+                return $node->getFileInfo()->getMountPoint()->getMountType();
360
+            });
361
+        }
362
+
363
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
364
+            $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) {
365
+                return $this->config->getSystemValue('data-fingerprint', '');
366
+            });
367
+        }
368
+
369
+        if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
370
+            $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) {
371
+                /** @var $node \OCA\DAV\Connector\Sabre\File */
372
+                try {
373
+                    $directDownloadUrl = $node->getDirectDownload();
374
+                    if (isset($directDownloadUrl['url'])) {
375
+                        return $directDownloadUrl['url'];
376
+                    }
377
+                } catch (StorageNotAvailableException $e) {
378
+                    return false;
379
+                } catch (ForbiddenException $e) {
380
+                    return false;
381
+                }
382
+                return false;
383
+            });
384
+
385
+            $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
386
+                $checksum = $node->getChecksum();
387
+                if ($checksum === NULL || $checksum === '') {
388
+                    return null;
389
+                }
390
+
391
+                return new ChecksumList($checksum);
392
+            });
393
+
394
+        }
395
+
396
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
397
+            $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
398
+                return $node->getSize();
399
+            });
400
+
401
+            $propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function() use ($node) {
402
+                return $node->getFileInfo()->isEncrypted() ? '1' : '0';
403
+            });
404
+        }
405
+    }
406
+
407
+    /**
408
+     * translate Nextcloud permissions to OCM Permissions
409
+     *
410
+     * @param $ncPermissions
411
+     * @return array
412
+     */
413
+    protected function ncPermissions2ocmPermissions($ncPermissions) {
414
+
415
+        $ocmPermissions = [];
416
+
417
+        if ($ncPermissions & Constants::PERMISSION_SHARE) {
418
+            $ocmPermissions[] = 'share';
419
+        }
420
+
421
+        if ($ncPermissions & Constants::PERMISSION_READ) {
422
+            $ocmPermissions[] = 'read';
423
+        }
424
+
425
+        if (($ncPermissions & Constants::PERMISSION_CREATE) ||
426
+            ($ncPermissions & Constants::PERMISSION_UPDATE)) {
427
+            $ocmPermissions[] = 'write';
428
+        }
429
+
430
+        return $ocmPermissions;
431
+
432
+    }
433
+
434
+    /**
435
+     * Update ownCloud-specific properties
436
+     *
437
+     * @param string $path
438
+     * @param PropPatch $propPatch
439
+     *
440
+     * @return void
441
+     */
442
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
443
+        $node = $this->tree->getNodeForPath($path);
444
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
445
+            return;
446
+        }
447
+
448
+        $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) {
449
+            if (empty($time)) {
450
+                return false;
451
+            }
452
+            $node->touch($time);
453
+            return true;
454
+        });
455
+        $propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) {
456
+            if (empty($etag)) {
457
+                return false;
458
+            }
459
+            if ($node->setEtag($etag) !== -1) {
460
+                return true;
461
+            }
462
+            return false;
463
+        });
464
+    }
465
+
466
+    /**
467
+     * @param string $filePath
468
+     * @param \Sabre\DAV\INode $node
469
+     * @throws \Sabre\DAV\Exception\BadRequest
470
+     */
471
+    public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
472
+        // chunked upload handling
473
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
474
+            list($path, $name) = \Sabre\Uri\split($filePath);
475
+            $info = \OC_FileChunking::decodeName($name);
476
+            if (!empty($info)) {
477
+                $filePath = $path . '/' . $info['name'];
478
+            }
479
+        }
480
+
481
+        // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
482
+        if (!$this->server->tree->nodeExists($filePath)) {
483
+            return;
484
+        }
485
+        $node = $this->server->tree->getNodeForPath($filePath);
486
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
487
+            $fileId = $node->getFileId();
488
+            if (!is_null($fileId)) {
489
+                $this->server->httpResponse->setHeader('OC-FileId', $fileId);
490
+            }
491
+        }
492
+    }
493 493
 }
Please login to merge, or discard this patch.