Passed
Push — master ( 54532b...9a1a14 )
by John
25:10 queued 12:32
created
apps/dav/lib/DAV/CustomPropertiesBackend.php 1 patch
Indentation   +336 added lines, -336 removed lines patch added patch discarded remove patch
@@ -35,340 +35,340 @@
 block discarded – undo
35 35
 
36 36
 class CustomPropertiesBackend implements BackendInterface {
37 37
 
38
-	/** @var string */
39
-	private const TABLE_NAME = 'properties';
40
-
41
-	/**
42
-	 * Ignored properties
43
-	 *
44
-	 * @var string[]
45
-	 */
46
-	private const IGNORED_PROPERTIES = [
47
-		'{DAV:}getcontentlength',
48
-		'{DAV:}getcontenttype',
49
-		'{DAV:}getetag',
50
-		'{DAV:}quota-used-bytes',
51
-		'{DAV:}quota-available-bytes',
52
-		'{http://owncloud.org/ns}permissions',
53
-		'{http://owncloud.org/ns}downloadURL',
54
-		'{http://owncloud.org/ns}dDC',
55
-		'{http://owncloud.org/ns}size',
56
-		'{http://nextcloud.org/ns}is-encrypted',
57
-
58
-		// Currently, returning null from any propfind handler would still trigger the backend,
59
-		// so we add all known Nextcloud custom properties in here to avoid that
60
-
61
-		// text app
62
-		'{http://nextcloud.org/ns}rich-workspace',
63
-		'{http://nextcloud.org/ns}rich-workspace-file',
64
-		// groupfolders
65
-		'{http://nextcloud.org/ns}acl-enabled',
66
-		'{http://nextcloud.org/ns}acl-can-manage',
67
-		'{http://nextcloud.org/ns}acl-list',
68
-		'{http://nextcloud.org/ns}inherited-acl-list',
69
-		'{http://nextcloud.org/ns}group-folder-id',
70
-		// files_lock
71
-		'{http://nextcloud.org/ns}lock',
72
-		'{http://nextcloud.org/ns}lock-owner-type',
73
-		'{http://nextcloud.org/ns}lock-owner',
74
-		'{http://nextcloud.org/ns}lock-owner-displayname',
75
-		'{http://nextcloud.org/ns}lock-owner-editor',
76
-		'{http://nextcloud.org/ns}lock-time',
77
-		'{http://nextcloud.org/ns}lock-timeout',
78
-		'{http://nextcloud.org/ns}lock-token',
79
-	];
80
-
81
-	/**
82
-	 * Properties set by one user, readable by all others
83
-	 *
84
-	 * @var array[]
85
-	 */
86
-	private const PUBLISHED_READ_ONLY_PROPERTIES = [
87
-		'{urn:ietf:params:xml:ns:caldav}calendar-availability',
88
-	];
89
-
90
-	/**
91
-	 * @var Tree
92
-	 */
93
-	private $tree;
94
-
95
-	/**
96
-	 * @var IDBConnection
97
-	 */
98
-	private $connection;
99
-
100
-	/**
101
-	 * @var IUser
102
-	 */
103
-	private $user;
104
-
105
-	/**
106
-	 * Properties cache
107
-	 *
108
-	 * @var array
109
-	 */
110
-	private $userCache = [];
111
-
112
-	/**
113
-	 * @param Tree $tree node tree
114
-	 * @param IDBConnection $connection database connection
115
-	 * @param IUser $user owner of the tree and properties
116
-	 */
117
-	public function __construct(
118
-		Tree $tree,
119
-		IDBConnection $connection,
120
-		IUser $user) {
121
-		$this->tree = $tree;
122
-		$this->connection = $connection;
123
-		$this->user = $user;
124
-	}
125
-
126
-	/**
127
-	 * Fetches properties for a path.
128
-	 *
129
-	 * @param string $path
130
-	 * @param PropFind $propFind
131
-	 * @return void
132
-	 */
133
-	public function propFind($path, PropFind $propFind) {
134
-		$requestedProps = $propFind->get404Properties();
135
-
136
-		// these might appear
137
-		$requestedProps = array_diff(
138
-			$requestedProps,
139
-			self::IGNORED_PROPERTIES
140
-		);
141
-
142
-		// substr of calendars/ => path is inside the CalDAV component
143
-		// two '/' => this a calendar (no calendar-home nor calendar object)
144
-		if (substr($path, 0, 10) === 'calendars/' && substr_count($path, '/') === 2) {
145
-			$allRequestedProps = $propFind->getRequestedProperties();
146
-			$customPropertiesForShares = [
147
-				'{DAV:}displayname',
148
-				'{urn:ietf:params:xml:ns:caldav}calendar-description',
149
-				'{urn:ietf:params:xml:ns:caldav}calendar-timezone',
150
-				'{http://apple.com/ns/ical/}calendar-order',
151
-				'{http://apple.com/ns/ical/}calendar-color',
152
-				'{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp',
153
-			];
154
-
155
-			foreach ($customPropertiesForShares as $customPropertyForShares) {
156
-				if (in_array($customPropertyForShares, $allRequestedProps)) {
157
-					$requestedProps[] = $customPropertyForShares;
158
-				}
159
-			}
160
-		}
161
-
162
-		if (empty($requestedProps)) {
163
-			return;
164
-		}
165
-
166
-		// First fetch the published properties (set by another user), then get the ones set by
167
-		// the current user. If both are set then the latter as priority.
168
-		foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) {
169
-			$propFind->set($propName, $propValue);
170
-		}
171
-		foreach ($this->getUserProperties($path, $requestedProps) as $propName => $propValue) {
172
-			$propFind->set($propName, $propValue);
173
-		}
174
-	}
175
-
176
-	/**
177
-	 * Updates properties for a path
178
-	 *
179
-	 * @param string $path
180
-	 * @param PropPatch $propPatch
181
-	 *
182
-	 * @return void
183
-	 */
184
-	public function propPatch($path, PropPatch $propPatch) {
185
-		$propPatch->handleRemaining(function ($changedProps) use ($path) {
186
-			return $this->updateProperties($path, $changedProps);
187
-		});
188
-	}
189
-
190
-	/**
191
-	 * This method is called after a node is deleted.
192
-	 *
193
-	 * @param string $path path of node for which to delete properties
194
-	 */
195
-	public function delete($path) {
196
-		$statement = $this->connection->prepare(
197
-			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
198
-		);
199
-		$statement->execute([$this->user->getUID(), $this->formatPath($path)]);
200
-		$statement->closeCursor();
201
-
202
-		unset($this->userCache[$path]);
203
-	}
204
-
205
-	/**
206
-	 * This method is called after a successful MOVE
207
-	 *
208
-	 * @param string $source
209
-	 * @param string $destination
210
-	 *
211
-	 * @return void
212
-	 */
213
-	public function move($source, $destination) {
214
-		$statement = $this->connection->prepare(
215
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
216
-			' WHERE `userid` = ? AND `propertypath` = ?'
217
-		);
218
-		$statement->execute([$this->formatPath($destination), $this->user->getUID(), $this->formatPath($source)]);
219
-		$statement->closeCursor();
220
-	}
221
-
222
-	/**
223
-	 * @param string $path
224
-	 * @param string[] $requestedProperties
225
-	 *
226
-	 * @return array
227
-	 */
228
-	private function getPublishedProperties(string $path, array $requestedProperties): array {
229
-		$allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties);
230
-
231
-		if (empty($allowedProps)) {
232
-			return [];
233
-		}
234
-
235
-		$qb = $this->connection->getQueryBuilder();
236
-		$qb->select('*')
237
-			->from(self::TABLE_NAME)
238
-			->where($qb->expr()->eq('propertypath', $qb->createNamedParameter($path)));
239
-		$result = $qb->executeQuery();
240
-		$props = [];
241
-		while ($row = $result->fetch()) {
242
-			$props[$row['propertyname']] = $row['propertyvalue'];
243
-		}
244
-		$result->closeCursor();
245
-		return $props;
246
-	}
247
-
248
-	/**
249
-	 * Returns a list of properties for the given path and current user
250
-	 *
251
-	 * @param string $path
252
-	 * @param array $requestedProperties requested properties or empty array for "all"
253
-	 * @return array
254
-	 * @note The properties list is a list of propertynames the client
255
-	 * requested, encoded as xmlnamespace#tagName, for example:
256
-	 * http://www.example.org/namespace#author If the array is empty, all
257
-	 * properties should be returned
258
-	 */
259
-	private function getUserProperties(string $path, array $requestedProperties) {
260
-		if (isset($this->userCache[$path])) {
261
-			return $this->userCache[$path];
262
-		}
263
-
264
-		// TODO: chunking if more than 1000 properties
265
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
266
-
267
-		$whereValues = [$this->user->getUID(), $this->formatPath($path)];
268
-		$whereTypes = [null, null];
269
-
270
-		if (!empty($requestedProperties)) {
271
-			// request only a subset
272
-			$sql .= ' AND `propertyname` in (?)';
273
-			$whereValues[] = $requestedProperties;
274
-			$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
275
-		}
276
-
277
-		$result = $this->connection->executeQuery(
278
-			$sql,
279
-			$whereValues,
280
-			$whereTypes
281
-		);
282
-
283
-		$props = [];
284
-		while ($row = $result->fetch()) {
285
-			$props[$row['propertyname']] = $row['propertyvalue'];
286
-		}
287
-
288
-		$result->closeCursor();
289
-
290
-		$this->userCache[$path] = $props;
291
-		return $props;
292
-	}
293
-
294
-	/**
295
-	 * Update properties
296
-	 *
297
-	 * @param string $path path for which to update properties
298
-	 * @param array $properties array of properties to update
299
-	 *
300
-	 * @return bool
301
-	 */
302
-	private function updateProperties(string $path, array $properties) {
303
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
304
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
305
-
306
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
307
-			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
308
-
309
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
310
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
311
-
312
-		// TODO: use "insert or update" strategy ?
313
-		$existing = $this->getUserProperties($path, []);
314
-		$this->connection->beginTransaction();
315
-		foreach ($properties as $propertyName => $propertyValue) {
316
-			// If it was null, we need to delete the property
317
-			if (is_null($propertyValue)) {
318
-				if (array_key_exists($propertyName, $existing)) {
319
-					$this->connection->executeUpdate($deleteStatement,
320
-						[
321
-							$this->user->getUID(),
322
-							$this->formatPath($path),
323
-							$propertyName,
324
-						]
325
-					);
326
-				}
327
-			} else {
328
-				if ($propertyValue instanceOf \Sabre\DAV\Xml\Property\Complex) {
329
-					$propertyValue = $propertyValue->getXml();
330
-				} elseif (!is_string($propertyValue)) {
331
-					$propertyValue = (string)$propertyValue;
332
-				}
333
-				if (!array_key_exists($propertyName, $existing)) {
334
-					$this->connection->executeUpdate($insertStatement,
335
-						[
336
-							$this->user->getUID(),
337
-							$this->formatPath($path),
338
-							$propertyName,
339
-							$propertyValue,
340
-						]
341
-					);
342
-				} else {
343
-					$this->connection->executeUpdate($updateStatement,
344
-						[
345
-							$propertyValue,
346
-							$this->user->getUID(),
347
-							$this->formatPath($path),
348
-							$propertyName,
349
-						]
350
-					);
351
-				}
352
-			}
353
-		}
354
-
355
-		$this->connection->commit();
356
-		unset($this->userCache[$path]);
357
-
358
-		return true;
359
-	}
360
-
361
-	/**
362
-	 * long paths are hashed to ensure they fit in the database
363
-	 *
364
-	 * @param string $path
365
-	 * @return string
366
-	 */
367
-	private function formatPath(string $path): string {
368
-		if (strlen($path) > 250) {
369
-			return sha1($path);
370
-		} else {
371
-			return $path;
372
-		}
373
-	}
38
+    /** @var string */
39
+    private const TABLE_NAME = 'properties';
40
+
41
+    /**
42
+     * Ignored properties
43
+     *
44
+     * @var string[]
45
+     */
46
+    private const IGNORED_PROPERTIES = [
47
+        '{DAV:}getcontentlength',
48
+        '{DAV:}getcontenttype',
49
+        '{DAV:}getetag',
50
+        '{DAV:}quota-used-bytes',
51
+        '{DAV:}quota-available-bytes',
52
+        '{http://owncloud.org/ns}permissions',
53
+        '{http://owncloud.org/ns}downloadURL',
54
+        '{http://owncloud.org/ns}dDC',
55
+        '{http://owncloud.org/ns}size',
56
+        '{http://nextcloud.org/ns}is-encrypted',
57
+
58
+        // Currently, returning null from any propfind handler would still trigger the backend,
59
+        // so we add all known Nextcloud custom properties in here to avoid that
60
+
61
+        // text app
62
+        '{http://nextcloud.org/ns}rich-workspace',
63
+        '{http://nextcloud.org/ns}rich-workspace-file',
64
+        // groupfolders
65
+        '{http://nextcloud.org/ns}acl-enabled',
66
+        '{http://nextcloud.org/ns}acl-can-manage',
67
+        '{http://nextcloud.org/ns}acl-list',
68
+        '{http://nextcloud.org/ns}inherited-acl-list',
69
+        '{http://nextcloud.org/ns}group-folder-id',
70
+        // files_lock
71
+        '{http://nextcloud.org/ns}lock',
72
+        '{http://nextcloud.org/ns}lock-owner-type',
73
+        '{http://nextcloud.org/ns}lock-owner',
74
+        '{http://nextcloud.org/ns}lock-owner-displayname',
75
+        '{http://nextcloud.org/ns}lock-owner-editor',
76
+        '{http://nextcloud.org/ns}lock-time',
77
+        '{http://nextcloud.org/ns}lock-timeout',
78
+        '{http://nextcloud.org/ns}lock-token',
79
+    ];
80
+
81
+    /**
82
+     * Properties set by one user, readable by all others
83
+     *
84
+     * @var array[]
85
+     */
86
+    private const PUBLISHED_READ_ONLY_PROPERTIES = [
87
+        '{urn:ietf:params:xml:ns:caldav}calendar-availability',
88
+    ];
89
+
90
+    /**
91
+     * @var Tree
92
+     */
93
+    private $tree;
94
+
95
+    /**
96
+     * @var IDBConnection
97
+     */
98
+    private $connection;
99
+
100
+    /**
101
+     * @var IUser
102
+     */
103
+    private $user;
104
+
105
+    /**
106
+     * Properties cache
107
+     *
108
+     * @var array
109
+     */
110
+    private $userCache = [];
111
+
112
+    /**
113
+     * @param Tree $tree node tree
114
+     * @param IDBConnection $connection database connection
115
+     * @param IUser $user owner of the tree and properties
116
+     */
117
+    public function __construct(
118
+        Tree $tree,
119
+        IDBConnection $connection,
120
+        IUser $user) {
121
+        $this->tree = $tree;
122
+        $this->connection = $connection;
123
+        $this->user = $user;
124
+    }
125
+
126
+    /**
127
+     * Fetches properties for a path.
128
+     *
129
+     * @param string $path
130
+     * @param PropFind $propFind
131
+     * @return void
132
+     */
133
+    public function propFind($path, PropFind $propFind) {
134
+        $requestedProps = $propFind->get404Properties();
135
+
136
+        // these might appear
137
+        $requestedProps = array_diff(
138
+            $requestedProps,
139
+            self::IGNORED_PROPERTIES
140
+        );
141
+
142
+        // substr of calendars/ => path is inside the CalDAV component
143
+        // two '/' => this a calendar (no calendar-home nor calendar object)
144
+        if (substr($path, 0, 10) === 'calendars/' && substr_count($path, '/') === 2) {
145
+            $allRequestedProps = $propFind->getRequestedProperties();
146
+            $customPropertiesForShares = [
147
+                '{DAV:}displayname',
148
+                '{urn:ietf:params:xml:ns:caldav}calendar-description',
149
+                '{urn:ietf:params:xml:ns:caldav}calendar-timezone',
150
+                '{http://apple.com/ns/ical/}calendar-order',
151
+                '{http://apple.com/ns/ical/}calendar-color',
152
+                '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp',
153
+            ];
154
+
155
+            foreach ($customPropertiesForShares as $customPropertyForShares) {
156
+                if (in_array($customPropertyForShares, $allRequestedProps)) {
157
+                    $requestedProps[] = $customPropertyForShares;
158
+                }
159
+            }
160
+        }
161
+
162
+        if (empty($requestedProps)) {
163
+            return;
164
+        }
165
+
166
+        // First fetch the published properties (set by another user), then get the ones set by
167
+        // the current user. If both are set then the latter as priority.
168
+        foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) {
169
+            $propFind->set($propName, $propValue);
170
+        }
171
+        foreach ($this->getUserProperties($path, $requestedProps) as $propName => $propValue) {
172
+            $propFind->set($propName, $propValue);
173
+        }
174
+    }
175
+
176
+    /**
177
+     * Updates properties for a path
178
+     *
179
+     * @param string $path
180
+     * @param PropPatch $propPatch
181
+     *
182
+     * @return void
183
+     */
184
+    public function propPatch($path, PropPatch $propPatch) {
185
+        $propPatch->handleRemaining(function ($changedProps) use ($path) {
186
+            return $this->updateProperties($path, $changedProps);
187
+        });
188
+    }
189
+
190
+    /**
191
+     * This method is called after a node is deleted.
192
+     *
193
+     * @param string $path path of node for which to delete properties
194
+     */
195
+    public function delete($path) {
196
+        $statement = $this->connection->prepare(
197
+            'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
198
+        );
199
+        $statement->execute([$this->user->getUID(), $this->formatPath($path)]);
200
+        $statement->closeCursor();
201
+
202
+        unset($this->userCache[$path]);
203
+    }
204
+
205
+    /**
206
+     * This method is called after a successful MOVE
207
+     *
208
+     * @param string $source
209
+     * @param string $destination
210
+     *
211
+     * @return void
212
+     */
213
+    public function move($source, $destination) {
214
+        $statement = $this->connection->prepare(
215
+            'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
216
+            ' WHERE `userid` = ? AND `propertypath` = ?'
217
+        );
218
+        $statement->execute([$this->formatPath($destination), $this->user->getUID(), $this->formatPath($source)]);
219
+        $statement->closeCursor();
220
+    }
221
+
222
+    /**
223
+     * @param string $path
224
+     * @param string[] $requestedProperties
225
+     *
226
+     * @return array
227
+     */
228
+    private function getPublishedProperties(string $path, array $requestedProperties): array {
229
+        $allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties);
230
+
231
+        if (empty($allowedProps)) {
232
+            return [];
233
+        }
234
+
235
+        $qb = $this->connection->getQueryBuilder();
236
+        $qb->select('*')
237
+            ->from(self::TABLE_NAME)
238
+            ->where($qb->expr()->eq('propertypath', $qb->createNamedParameter($path)));
239
+        $result = $qb->executeQuery();
240
+        $props = [];
241
+        while ($row = $result->fetch()) {
242
+            $props[$row['propertyname']] = $row['propertyvalue'];
243
+        }
244
+        $result->closeCursor();
245
+        return $props;
246
+    }
247
+
248
+    /**
249
+     * Returns a list of properties for the given path and current user
250
+     *
251
+     * @param string $path
252
+     * @param array $requestedProperties requested properties or empty array for "all"
253
+     * @return array
254
+     * @note The properties list is a list of propertynames the client
255
+     * requested, encoded as xmlnamespace#tagName, for example:
256
+     * http://www.example.org/namespace#author If the array is empty, all
257
+     * properties should be returned
258
+     */
259
+    private function getUserProperties(string $path, array $requestedProperties) {
260
+        if (isset($this->userCache[$path])) {
261
+            return $this->userCache[$path];
262
+        }
263
+
264
+        // TODO: chunking if more than 1000 properties
265
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
266
+
267
+        $whereValues = [$this->user->getUID(), $this->formatPath($path)];
268
+        $whereTypes = [null, null];
269
+
270
+        if (!empty($requestedProperties)) {
271
+            // request only a subset
272
+            $sql .= ' AND `propertyname` in (?)';
273
+            $whereValues[] = $requestedProperties;
274
+            $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
275
+        }
276
+
277
+        $result = $this->connection->executeQuery(
278
+            $sql,
279
+            $whereValues,
280
+            $whereTypes
281
+        );
282
+
283
+        $props = [];
284
+        while ($row = $result->fetch()) {
285
+            $props[$row['propertyname']] = $row['propertyvalue'];
286
+        }
287
+
288
+        $result->closeCursor();
289
+
290
+        $this->userCache[$path] = $props;
291
+        return $props;
292
+    }
293
+
294
+    /**
295
+     * Update properties
296
+     *
297
+     * @param string $path path for which to update properties
298
+     * @param array $properties array of properties to update
299
+     *
300
+     * @return bool
301
+     */
302
+    private function updateProperties(string $path, array $properties) {
303
+        $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
304
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
305
+
306
+        $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
307
+            ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
308
+
309
+        $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
310
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
311
+
312
+        // TODO: use "insert or update" strategy ?
313
+        $existing = $this->getUserProperties($path, []);
314
+        $this->connection->beginTransaction();
315
+        foreach ($properties as $propertyName => $propertyValue) {
316
+            // If it was null, we need to delete the property
317
+            if (is_null($propertyValue)) {
318
+                if (array_key_exists($propertyName, $existing)) {
319
+                    $this->connection->executeUpdate($deleteStatement,
320
+                        [
321
+                            $this->user->getUID(),
322
+                            $this->formatPath($path),
323
+                            $propertyName,
324
+                        ]
325
+                    );
326
+                }
327
+            } else {
328
+                if ($propertyValue instanceOf \Sabre\DAV\Xml\Property\Complex) {
329
+                    $propertyValue = $propertyValue->getXml();
330
+                } elseif (!is_string($propertyValue)) {
331
+                    $propertyValue = (string)$propertyValue;
332
+                }
333
+                if (!array_key_exists($propertyName, $existing)) {
334
+                    $this->connection->executeUpdate($insertStatement,
335
+                        [
336
+                            $this->user->getUID(),
337
+                            $this->formatPath($path),
338
+                            $propertyName,
339
+                            $propertyValue,
340
+                        ]
341
+                    );
342
+                } else {
343
+                    $this->connection->executeUpdate($updateStatement,
344
+                        [
345
+                            $propertyValue,
346
+                            $this->user->getUID(),
347
+                            $this->formatPath($path),
348
+                            $propertyName,
349
+                        ]
350
+                    );
351
+                }
352
+            }
353
+        }
354
+
355
+        $this->connection->commit();
356
+        unset($this->userCache[$path]);
357
+
358
+        return true;
359
+    }
360
+
361
+    /**
362
+     * long paths are hashed to ensure they fit in the database
363
+     *
364
+     * @param string $path
365
+     * @return string
366
+     */
367
+    private function formatPath(string $path): string {
368
+        if (strlen($path) > 250) {
369
+            return sha1($path);
370
+        } else {
371
+            return $path;
372
+        }
373
+    }
374 374
 }
Please login to merge, or discard this patch.