Completed
Push — master ( ed1647...1b22ca )
by
unknown
17:50
created
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +1137 added lines, -1137 removed lines patch added patch discarded remove patch
@@ -50,1141 +50,1141 @@
 block discarded – undo
50 50
 
51 51
 /** @template-implements IEventListener<BeforeNodeDeletedEvent> */
52 52
 class Trashbin implements IEventListener {
53
-	// unit: percentage; 50% of available disk space/quota
54
-	public const DEFAULTMAXSIZE = 50;
55
-
56
-	/**
57
-	 * Ensure we don't need to scan the file during the move to trash
58
-	 * by triggering the scan in the pre-hook
59
-	 */
60
-	public static function ensureFileScannedHook(Node $node): void {
61
-		try {
62
-			self::getUidAndFilename($node->getPath());
63
-		} catch (NotFoundException $e) {
64
-			// Nothing to scan for non existing files
65
-		}
66
-	}
67
-
68
-	/**
69
-	 * get the UID of the owner of the file and the path to the file relative to
70
-	 * owners files folder
71
-	 *
72
-	 * @param string $filename
73
-	 * @return array
74
-	 * @throws NoUserException
75
-	 */
76
-	public static function getUidAndFilename($filename) {
77
-		$uid = Filesystem::getOwner($filename);
78
-		$userManager = Server::get(IUserManager::class);
79
-		// if the user with the UID doesn't exists, e.g. because the UID points
80
-		// to a remote user with a federated cloud ID we use the current logged-in
81
-		// user. We need a valid local user to move the file to the right trash bin
82
-		if (!$userManager->userExists($uid)) {
83
-			$uid = OC_User::getUser();
84
-		}
85
-		if (!$uid) {
86
-			// no owner, usually because of share link from ext storage
87
-			return [null, null];
88
-		}
89
-		Filesystem::initMountPoints($uid);
90
-		if ($uid !== OC_User::getUser()) {
91
-			$info = Filesystem::getFileInfo($filename);
92
-			$ownerView = new View('/' . $uid . '/files');
93
-			try {
94
-				$filename = $ownerView->getPath($info['fileid']);
95
-			} catch (NotFoundException $e) {
96
-				$filename = null;
97
-			}
98
-		}
99
-		return [$uid, $filename];
100
-	}
101
-
102
-	/**
103
-	 * get original location and deleted by of files for user
104
-	 *
105
-	 * @param string $user
106
-	 * @return array<string, array<string, array{location: string, deletedBy: string}>>
107
-	 */
108
-	public static function getExtraData($user) {
109
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
110
-		$query->select('id', 'timestamp', 'location', 'deleted_by')
111
-			->from('files_trash')
112
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
113
-		$result = $query->executeQuery();
114
-		$array = [];
115
-		while ($row = $result->fetch()) {
116
-			$array[$row['id']][$row['timestamp']] = [
117
-				'location' => (string)$row['location'],
118
-				'deletedBy' => (string)$row['deleted_by'],
119
-			];
120
-		}
121
-		$result->closeCursor();
122
-		return $array;
123
-	}
124
-
125
-	/**
126
-	 * get original location of file
127
-	 *
128
-	 * @param string $user
129
-	 * @param string $filename
130
-	 * @param string $timestamp
131
-	 * @return string|false original location
132
-	 */
133
-	public static function getLocation($user, $filename, $timestamp) {
134
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
135
-		$query->select('location')
136
-			->from('files_trash')
137
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)))
138
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
139
-			->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
140
-
141
-		$result = $query->executeQuery();
142
-		$row = $result->fetch();
143
-		$result->closeCursor();
144
-
145
-		if (isset($row['location'])) {
146
-			return $row['location'];
147
-		} else {
148
-			return false;
149
-		}
150
-	}
151
-
152
-	/** @param string $user */
153
-	private static function setUpTrash($user): void {
154
-		$view = new View('/' . $user);
155
-		if (!$view->is_dir('files_trashbin')) {
156
-			$view->mkdir('files_trashbin');
157
-		}
158
-		if (!$view->is_dir('files_trashbin/files')) {
159
-			$view->mkdir('files_trashbin/files');
160
-		}
161
-		if (!$view->is_dir('files_trashbin/versions')) {
162
-			$view->mkdir('files_trashbin/versions');
163
-		}
164
-		if (!$view->is_dir('files_trashbin/keys')) {
165
-			$view->mkdir('files_trashbin/keys');
166
-		}
167
-	}
168
-
169
-
170
-	/**
171
-	 * copy file to owners trash
172
-	 *
173
-	 * @param string $sourcePath
174
-	 * @param string $owner
175
-	 * @param string $targetPath
176
-	 * @param string $user
177
-	 * @param int $timestamp
178
-	 */
179
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void {
180
-		self::setUpTrash($owner);
181
-
182
-		$targetFilename = basename($targetPath);
183
-		$targetLocation = dirname($targetPath);
184
-
185
-		$sourceFilename = basename($sourcePath);
186
-
187
-		$view = new View('/');
188
-
189
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
190
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
191
-		$free = $view->free_space($target);
192
-		$isUnknownOrUnlimitedFreeSpace = $free < 0;
193
-		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
194
-		if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
195
-			self::copy_recursive($source, $target, $view);
196
-		}
197
-
198
-
199
-		if ($view->file_exists($target)) {
200
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
201
-			$query->insert('files_trash')
202
-				->setValue('id', $query->createNamedParameter($targetFilename))
203
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
204
-				->setValue('location', $query->createNamedParameter($targetLocation))
205
-				->setValue('user', $query->createNamedParameter($user))
206
-				->setValue('deleted_by', $query->createNamedParameter($user));
207
-			$result = $query->executeStatement();
208
-			if (!$result) {
209
-				Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
210
-			}
211
-		}
212
-	}
213
-
214
-
215
-	/**
216
-	 * move file to the trash bin
217
-	 *
218
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
219
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
220
-	 *
221
-	 * @return bool
222
-	 */
223
-	public static function move2trash($file_path, $ownerOnly = false) {
224
-		// get the user for which the filesystem is setup
225
-		$root = Filesystem::getRoot();
226
-		[, $user] = explode('/', $root);
227
-		[$owner, $ownerPath] = self::getUidAndFilename($file_path);
228
-
229
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
230
-		if (is_null($owner)) {
231
-			$owner = $user;
232
-			$ownerPath = $file_path;
233
-		}
234
-
235
-		$ownerView = new View('/' . $owner);
236
-
237
-		// file has been deleted in between
238
-		if (is_null($ownerPath) || $ownerPath === '') {
239
-			return true;
240
-		}
241
-
242
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
243
-
244
-		if ($sourceInfo === false) {
245
-			return true;
246
-		}
247
-
248
-		self::setUpTrash($user);
249
-		if ($owner !== $user) {
250
-			// also setup for owner
251
-			self::setUpTrash($owner);
252
-		}
253
-
254
-		$path_parts = pathinfo($ownerPath);
255
-
256
-		$filename = $path_parts['basename'];
257
-		$location = $path_parts['dirname'];
258
-		/** @var ITimeFactory $timeFactory */
259
-		$timeFactory = Server::get(ITimeFactory::class);
260
-		$timestamp = $timeFactory->getTime();
261
-
262
-		$lockingProvider = Server::get(ILockingProvider::class);
263
-
264
-		// disable proxy to prevent recursive calls
265
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
266
-		$gotLock = false;
267
-
268
-		do {
269
-			/** @var ILockingStorage & IStorage $trashStorage */
270
-			[$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
271
-			try {
272
-				$trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
273
-				$gotLock = true;
274
-			} catch (LockedException $e) {
275
-				// a file with the same name is being deleted concurrently
276
-				// nudge the timestamp a bit to resolve the conflict
277
-
278
-				$timestamp = $timestamp + 1;
279
-
280
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
281
-			}
282
-		} while (!$gotLock);
283
-
284
-		$sourceStorage = $sourceInfo->getStorage();
285
-		$sourceInternalPath = $sourceInfo->getInternalPath();
286
-
287
-		if ($trashStorage->file_exists($trashInternalPath)) {
288
-			$trashStorage->unlink($trashInternalPath);
289
-		}
290
-
291
-		$configuredTrashbinSize = static::getConfiguredTrashbinSize($owner);
292
-		if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
293
-			return false;
294
-		}
295
-
296
-		try {
297
-			$moveSuccessful = true;
298
-
299
-			$inCache = $sourceStorage->getCache()->inCache($sourceInternalPath);
300
-			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
301
-			if ($inCache) {
302
-				$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
303
-			}
304
-		} catch (CopyRecursiveException $e) {
305
-			$moveSuccessful = false;
306
-			if ($trashStorage->file_exists($trashInternalPath)) {
307
-				$trashStorage->unlink($trashInternalPath);
308
-			}
309
-			Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
310
-		}
311
-
312
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
313
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
314
-				$sourceStorage->rmdir($sourceInternalPath);
315
-			} else {
316
-				$sourceStorage->unlink($sourceInternalPath);
317
-			}
318
-
319
-			if ($sourceStorage->file_exists($sourceInternalPath)) {
320
-				// undo the cache move
321
-				$sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
322
-			} else {
323
-				$trashStorage->getUpdater()->remove($trashInternalPath);
324
-			}
325
-			return false;
326
-		}
327
-
328
-		if ($moveSuccessful) {
329
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
330
-			$query->insert('files_trash')
331
-				->setValue('id', $query->createNamedParameter($filename))
332
-				->setValue('timestamp', $query->createNamedParameter($timestamp))
333
-				->setValue('location', $query->createNamedParameter($location))
334
-				->setValue('user', $query->createNamedParameter($owner))
335
-				->setValue('deleted_by', $query->createNamedParameter($user));
336
-			$result = $query->executeStatement();
337
-			if (!$result) {
338
-				Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
339
-			}
340
-			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
341
-				'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
342
-
343
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
344
-
345
-			// if owner !== user we need to also add a copy to the users trash
346
-			if ($user !== $owner && $ownerOnly === false) {
347
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
348
-			}
349
-		}
350
-
351
-		$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
352
-
353
-		self::scheduleExpire($user);
354
-
355
-		// if owner !== user we also need to update the owners trash size
356
-		if ($owner !== $user) {
357
-			self::scheduleExpire($owner);
358
-		}
359
-
360
-		return $moveSuccessful;
361
-	}
362
-
363
-	private static function getConfiguredTrashbinSize(string $user): int|float {
364
-		$config = Server::get(IConfig::class);
365
-		$userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
366
-		if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
367
-			return Util::numericToNumber($userTrashbinSize);
368
-		}
369
-		$systemTrashbinSize = $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
370
-		if (is_numeric($systemTrashbinSize)) {
371
-			return Util::numericToNumber($systemTrashbinSize);
372
-		}
373
-		return -1;
374
-	}
375
-
376
-	/**
377
-	 * Move file versions to trash so that they can be restored later
378
-	 *
379
-	 * @param string $filename of deleted file
380
-	 * @param string $owner owner user id
381
-	 * @param string $ownerPath path relative to the owner's home storage
382
-	 * @param int $timestamp when the file was deleted
383
-	 */
384
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
385
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
386
-			$user = OC_User::getUser();
387
-			$rootView = new View('/');
388
-
389
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
390
-				if ($owner !== $user) {
391
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
392
-				}
393
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
394
-			} elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
395
-				foreach ($versions as $v) {
396
-					if ($owner !== $user) {
397
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
398
-					}
399
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
400
-				}
401
-			}
402
-		}
403
-	}
404
-
405
-	/**
406
-	 * Move a file or folder on storage level
407
-	 *
408
-	 * @param View $view
409
-	 * @param string $source
410
-	 * @param string $target
411
-	 * @return bool
412
-	 */
413
-	private static function move(View $view, $source, $target) {
414
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
415
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
416
-		/** @var \OC\Files\Storage\Storage $targetStorage */
417
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
418
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
419
-
420
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
421
-		if ($result) {
422
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
423
-		}
424
-		return $result;
425
-	}
426
-
427
-	/**
428
-	 * Copy a file or folder on storage level
429
-	 *
430
-	 * @param View $view
431
-	 * @param string $source
432
-	 * @param string $target
433
-	 * @return bool
434
-	 */
435
-	private static function copy(View $view, $source, $target) {
436
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
437
-		[$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
438
-		/** @var \OC\Files\Storage\Storage $targetStorage */
439
-		[$targetStorage, $targetInternalPath] = $view->resolvePath($target);
440
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
441
-
442
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
443
-		if ($result) {
444
-			$targetStorage->getUpdater()->update($targetInternalPath);
445
-		}
446
-		return $result;
447
-	}
448
-
449
-	/**
450
-	 * Restore a file or folder from trash bin
451
-	 *
452
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
453
-	 *                     including the timestamp suffix ".d12345678"
454
-	 * @param string $filename name of the file/folder
455
-	 * @param int $timestamp time when the file/folder was deleted
456
-	 *
457
-	 * @return bool true on success, false otherwise
458
-	 */
459
-	public static function restore($file, $filename, $timestamp) {
460
-		$user = OC_User::getUser();
461
-		if (!$user) {
462
-			throw new \Exception('Tried to restore a file while not logged in');
463
-		}
464
-		$view = new View('/' . $user);
465
-
466
-		$location = '';
467
-		if ($timestamp) {
468
-			$location = self::getLocation($user, $filename, $timestamp);
469
-			if ($location === false) {
470
-				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
471
-			} else {
472
-				// if location no longer exists, restore file in the root directory
473
-				if ($location !== '/'
474
-					&& (!$view->is_dir('files/' . $location)
475
-						|| !$view->isCreatable('files/' . $location))
476
-				) {
477
-					$location = '';
478
-				}
479
-			}
480
-		}
481
-
482
-		// we need a  extension in case a file/dir with the same name already exists
483
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
484
-
485
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
486
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
487
-		if (!$view->file_exists($source)) {
488
-			return false;
489
-		}
490
-		$mtime = $view->filemtime($source);
491
-
492
-		// restore file
493
-		if (!$view->isCreatable(dirname($target))) {
494
-			throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
495
-		}
496
-
497
-		$sourcePath = Filesystem::normalizePath($file);
498
-		$targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
499
-
500
-		$sourceNode = self::getNodeForPath($user, $sourcePath);
501
-		$targetNode = self::getNodeForPath($user, $targetPath, 'files');
502
-		$run = true;
503
-		$event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run);
504
-		$dispatcher = Server::get(IEventDispatcher::class);
505
-		$dispatcher->dispatchTyped($event);
506
-
507
-		if (!$run) {
508
-			return false;
509
-		}
510
-
511
-		$restoreResult = $view->rename($source, $target);
512
-
513
-		// handle the restore result
514
-		if ($restoreResult) {
515
-			$fakeRoot = $view->getRoot();
516
-			$view->chroot('/' . $user . '/files');
517
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
518
-			$view->chroot($fakeRoot);
519
-			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
520
-
521
-			$sourceNode = self::getNodeForPath($user, $sourcePath);
522
-			$targetNode = self::getNodeForPath($user, $targetPath, 'files');
523
-			$event = new NodeRestoredEvent($sourceNode, $targetNode);
524
-			$dispatcher = Server::get(IEventDispatcher::class);
525
-			$dispatcher->dispatchTyped($event);
526
-
527
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
528
-
529
-			if ($timestamp) {
530
-				$query = Server::get(IDBConnection::class)->getQueryBuilder();
531
-				$query->delete('files_trash')
532
-					->where($query->expr()->eq('user', $query->createNamedParameter($user)))
533
-					->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
534
-					->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
535
-				$query->executeStatement();
536
-			}
537
-
538
-			return true;
539
-		}
540
-
541
-		return false;
542
-	}
543
-
544
-	/**
545
-	 * restore versions from trash bin
546
-	 *
547
-	 * @param View $view file view
548
-	 * @param string $file complete path to file
549
-	 * @param string $filename name of file once it was deleted
550
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
551
-	 * @param string $location location if file
552
-	 * @param int $timestamp deletion time
553
-	 * @return false|null
554
-	 */
555
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
556
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
557
-			$user = OC_User::getUser();
558
-			$rootView = new View('/');
559
-
560
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
561
-
562
-			[$owner, $ownerPath] = self::getUidAndFilename($target);
563
-
564
-			// file has been deleted in between
565
-			if (empty($ownerPath)) {
566
-				return false;
567
-			}
568
-
569
-			if ($timestamp) {
570
-				$versionedFile = $filename;
571
-			} else {
572
-				$versionedFile = $file;
573
-			}
574
-
575
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
576
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
577
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
578
-				foreach ($versions as $v) {
579
-					if ($timestamp) {
580
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
581
-					} else {
582
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
583
-					}
584
-				}
585
-			}
586
-		}
587
-	}
588
-
589
-	/**
590
-	 * delete all files from the trash
591
-	 */
592
-	public static function deleteAll() {
593
-		$user = OC_User::getUser();
594
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
595
-		$view = new View('/' . $user);
596
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
597
-
598
-		try {
599
-			$trash = $userRoot->get('files_trashbin');
600
-		} catch (NotFoundException $e) {
601
-			return false;
602
-		}
603
-
604
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
605
-		$filePaths = [];
606
-		foreach ($fileInfos as $fileInfo) {
607
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
608
-		}
609
-		unset($fileInfos); // save memory
610
-
611
-		// Bulk PreDelete-Hook
612
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
613
-
614
-		// Single-File Hooks
615
-		foreach ($filePaths as $path) {
616
-			self::emitTrashbinPreDelete($path);
617
-		}
618
-
619
-		// actual file deletion
620
-		$trash->delete();
621
-
622
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
623
-		$query->delete('files_trash')
624
-			->where($query->expr()->eq('user', $query->createNamedParameter($user)));
625
-		$query->executeStatement();
626
-
627
-		// Bulk PostDelete-Hook
628
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
629
-
630
-		// Single-File Hooks
631
-		foreach ($filePaths as $path) {
632
-			self::emitTrashbinPostDelete($path);
633
-		}
634
-
635
-		$trash = $userRoot->newFolder('files_trashbin');
636
-		$trash->newFolder('files');
637
-
638
-		return true;
639
-	}
640
-
641
-	/**
642
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
643
-	 *
644
-	 * @param string $path
645
-	 */
646
-	protected static function emitTrashbinPreDelete($path) {
647
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
648
-	}
649
-
650
-	/**
651
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
652
-	 *
653
-	 * @param string $path
654
-	 */
655
-	protected static function emitTrashbinPostDelete($path) {
656
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
657
-	}
658
-
659
-	/**
660
-	 * delete file from trash bin permanently
661
-	 *
662
-	 * @param string $filename path to the file
663
-	 * @param string $user
664
-	 * @param int $timestamp of deletion time
665
-	 *
666
-	 * @return int|float size of deleted files
667
-	 */
668
-	public static function delete($filename, $user, $timestamp = null) {
669
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
670
-		$view = new View('/' . $user);
671
-		$size = 0;
672
-
673
-		if ($timestamp) {
674
-			$query = Server::get(IDBConnection::class)->getQueryBuilder();
675
-			$query->delete('files_trash')
676
-				->where($query->expr()->eq('user', $query->createNamedParameter($user)))
677
-				->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
678
-				->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
679
-			$query->executeStatement();
680
-
681
-			$file = static::getTrashFilename($filename, $timestamp);
682
-		} else {
683
-			$file = $filename;
684
-		}
685
-
686
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
687
-
688
-		try {
689
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
690
-		} catch (NotFoundException $e) {
691
-			return $size;
692
-		}
693
-
694
-		if ($node instanceof Folder) {
695
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
696
-		} elseif ($node instanceof File) {
697
-			$size += $view->filesize('/files_trashbin/files/' . $file);
698
-		}
699
-
700
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
701
-		$node->delete();
702
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
703
-
704
-		return $size;
705
-	}
706
-
707
-	/**
708
-	 * @param string $file
709
-	 * @param string $filename
710
-	 * @param ?int $timestamp
711
-	 */
712
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
713
-		$size = 0;
714
-		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
715
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
716
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
717
-				$view->unlink('files_trashbin/versions/' . $file);
718
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
719
-				foreach ($versions as $v) {
720
-					if ($timestamp) {
721
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
722
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
723
-					} else {
724
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
725
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
726
-					}
727
-				}
728
-			}
729
-		}
730
-		return $size;
731
-	}
732
-
733
-	/**
734
-	 * check to see whether a file exists in trashbin
735
-	 *
736
-	 * @param string $filename path to the file
737
-	 * @param int $timestamp of deletion time
738
-	 * @return bool true if file exists, otherwise false
739
-	 */
740
-	public static function file_exists($filename, $timestamp = null) {
741
-		$user = OC_User::getUser();
742
-		$view = new View('/' . $user);
743
-
744
-		if ($timestamp) {
745
-			$filename = static::getTrashFilename($filename, $timestamp);
746
-		}
747
-
748
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
749
-		return $view->file_exists($target);
750
-	}
751
-
752
-	/**
753
-	 * deletes used space for trash bin in db if user was deleted
754
-	 *
755
-	 * @param string $uid id of deleted user
756
-	 * @return bool result of db delete operation
757
-	 */
758
-	public static function deleteUser($uid) {
759
-		$query = Server::get(IDBConnection::class)->getQueryBuilder();
760
-		$query->delete('files_trash')
761
-			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
762
-		return (bool)$query->executeStatement();
763
-	}
764
-
765
-	/**
766
-	 * calculate remaining free space for trash bin
767
-	 *
768
-	 * @param int|float $trashbinSize current size of the trash bin
769
-	 * @param string $user
770
-	 * @return int|float available free space for trash bin
771
-	 */
772
-	private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
773
-		$configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
774
-		if ($configuredTrashbinSize > -1) {
775
-			return $configuredTrashbinSize - $trashbinSize;
776
-		}
777
-
778
-		$userObject = Server::get(IUserManager::class)->get($user);
779
-		if (is_null($userObject)) {
780
-			return 0;
781
-		}
782
-		$softQuota = true;
783
-		$quota = $userObject->getQuota();
784
-		if ($quota === null || $quota === 'none') {
785
-			$quota = Filesystem::free_space('/');
786
-			$softQuota = false;
787
-			// inf or unknown free space
788
-			if ($quota < 0) {
789
-				$quota = PHP_INT_MAX;
790
-			}
791
-		} else {
792
-			$quota = Util::computerFileSize($quota);
793
-			// invalid quota
794
-			if ($quota === false) {
795
-				$quota = PHP_INT_MAX;
796
-			}
797
-		}
798
-
799
-		// calculate available space for trash bin
800
-		// subtract size of files and current trash bin size from quota
801
-		if ($softQuota) {
802
-			$userFolder = \OC::$server->getUserFolder($user);
803
-			if (is_null($userFolder)) {
804
-				return 0;
805
-			}
806
-			$free = $quota - $userFolder->getSize(false); // remaining free space for user
807
-			if ($free > 0) {
808
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
809
-			} else {
810
-				$availableSpace = $free - $trashbinSize;
811
-			}
812
-		} else {
813
-			$availableSpace = $quota;
814
-		}
815
-
816
-		return Util::numericToNumber($availableSpace);
817
-	}
818
-
819
-	/**
820
-	 * resize trash bin if necessary after a new file was added to Nextcloud
821
-	 *
822
-	 * @param string $user user id
823
-	 */
824
-	public static function resizeTrash($user) {
825
-		$size = self::getTrashbinSize($user);
826
-
827
-		$freeSpace = self::calculateFreeSpace($size, $user);
828
-
829
-		if ($freeSpace < 0) {
830
-			self::scheduleExpire($user);
831
-		}
832
-	}
833
-
834
-	/**
835
-	 * clean up the trash bin
836
-	 *
837
-	 * @param string $user
838
-	 */
839
-	public static function expire($user) {
840
-		$trashBinSize = self::getTrashbinSize($user);
841
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
842
-
843
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
844
-
845
-		// delete all files older then $retention_obligation
846
-		[$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
847
-
848
-		$availableSpace += $delSize;
849
-
850
-		// delete files from trash until we meet the trash bin size limit again
851
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
852
-	}
853
-
854
-	/**
855
-	 * @param string $user
856
-	 */
857
-	private static function scheduleExpire($user) {
858
-		// let the admin disable auto expire
859
-		$expiration = Server::get(Expiration::class);
860
-		if ($expiration->isEnabled()) {
861
-			Server::get(IBus::class)->push(new Expire($user));
862
-		}
863
-	}
864
-
865
-	/**
866
-	 * if the size limit for the trash bin is reached, we delete the oldest
867
-	 * files in the trash bin until we meet the limit again
868
-	 *
869
-	 * @param array $files
870
-	 * @param string $user
871
-	 * @param int|float $availableSpace available disc space
872
-	 * @return int|float size of deleted files
873
-	 */
874
-	protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
875
-		$expiration = Server::get(Expiration::class);
876
-		$size = 0;
877
-
878
-		if ($availableSpace <= 0) {
879
-			foreach ($files as $file) {
880
-				if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
881
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
882
-					Server::get(LoggerInterface::class)->info(
883
-						'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
884
-						[
885
-							'app' => 'files_trashbin',
886
-							'user' => $user,
887
-						]
888
-					);
889
-					$availableSpace += $tmp;
890
-					$size += $tmp;
891
-				} else {
892
-					break;
893
-				}
894
-			}
895
-		}
896
-		return $size;
897
-	}
898
-
899
-	/**
900
-	 * delete files older then max storage time
901
-	 *
902
-	 * @param array $files list of files sorted by mtime
903
-	 * @param string $user
904
-	 * @return array{int|float, int} size of deleted files and number of deleted files
905
-	 */
906
-	public static function deleteExpiredFiles($files, $user) {
907
-		$expiration = Server::get(Expiration::class);
908
-		$size = 0;
909
-		$count = 0;
910
-		foreach ($files as $file) {
911
-			$timestamp = $file['mtime'];
912
-			$filename = $file['name'];
913
-			if ($expiration->isExpired($timestamp)) {
914
-				try {
915
-					$size += self::delete($filename, $user, $timestamp);
916
-					$count++;
917
-				} catch (NotPermittedException $e) {
918
-					Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
919
-						[
920
-							'exception' => $e,
921
-							'app' => 'files_trashbin',
922
-							'user' => $user,
923
-						]
924
-					);
925
-				}
926
-				Server::get(LoggerInterface::class)->info(
927
-					'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
928
-					[
929
-						'app' => 'files_trashbin',
930
-						'user' => $user,
931
-					],
932
-				);
933
-			} else {
934
-				break;
935
-			}
936
-		}
937
-
938
-		return [$size, $count];
939
-	}
940
-
941
-	/**
942
-	 * recursive copy to copy a whole directory
943
-	 *
944
-	 * @param string $source source path, relative to the users files directory
945
-	 * @param string $destination destination path relative to the users root directory
946
-	 * @param View $view file view for the users root directory
947
-	 * @return int|float
948
-	 * @throws Exceptions\CopyRecursiveException
949
-	 */
950
-	private static function copy_recursive($source, $destination, View $view): int|float {
951
-		$size = 0;
952
-		if ($view->is_dir($source)) {
953
-			$view->mkdir($destination);
954
-			$view->touch($destination, $view->filemtime($source));
955
-			foreach ($view->getDirectoryContent($source) as $i) {
956
-				$pathDir = $source . '/' . $i['name'];
957
-				if ($view->is_dir($pathDir)) {
958
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
959
-				} else {
960
-					$size += $view->filesize($pathDir);
961
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
962
-					if (!$result) {
963
-						throw new CopyRecursiveException();
964
-					}
965
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
966
-				}
967
-			}
968
-		} else {
969
-			$size += $view->filesize($source);
970
-			$result = $view->copy($source, $destination);
971
-			if (!$result) {
972
-				throw new CopyRecursiveException();
973
-			}
974
-			$view->touch($destination, $view->filemtime($source));
975
-		}
976
-		return $size;
977
-	}
978
-
979
-	/**
980
-	 * find all versions which belong to the file we want to restore
981
-	 *
982
-	 * @param string $filename name of the file which should be restored
983
-	 * @param int $timestamp timestamp when the file was deleted
984
-	 */
985
-	private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
986
-		$view = new View('/' . $user . '/files_trashbin/versions');
987
-		$versions = [];
988
-
989
-		/** @var \OC\Files\Storage\Storage $storage */
990
-		[$storage,] = $view->resolvePath('/');
991
-
992
-		$pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
993
-		if ($timestamp) {
994
-			// fetch for old versions
995
-			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
996
-			$pattern .= '.v%.d' . $escapedTimestamp;
997
-			$offset = -strlen($escapedTimestamp) - 2;
998
-		} else {
999
-			$pattern .= '.v%';
1000
-		}
1001
-
1002
-		// Manually fetch all versions from the file cache to be able to filter them by their parent
1003
-		$cache = $storage->getCache('');
1004
-		$query = new CacheQueryBuilder(
1005
-			Server::get(IDBConnection::class)->getQueryBuilder(),
1006
-			Server::get(IFilesMetadataManager::class),
1007
-		);
1008
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1009
-		$parentId = $cache->getId($normalizedParentPath);
1010
-		if ($parentId === -1) {
1011
-			return [];
1012
-		}
1013
-
1014
-		$query->selectFileCache()
1015
-			->whereStorageId($cache->getNumericStorageId())
1016
-			->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1017
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1018
-
1019
-		$result = $query->executeQuery();
1020
-		$entries = $result->fetchAll();
1021
-		$result->closeCursor();
1022
-
1023
-		/** @var CacheEntry[] $matches */
1024
-		$matches = array_map(function (array $data) {
1025
-			return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1026
-		}, $entries);
1027
-
1028
-		foreach ($matches as $ma) {
1029
-			if ($timestamp) {
1030
-				$parts = explode('.v', substr($ma['path'], 0, $offset));
1031
-				$versions[] = end($parts);
1032
-			} else {
1033
-				$parts = explode('.v', $ma['path']);
1034
-				$versions[] = end($parts);
1035
-			}
1036
-		}
1037
-
1038
-		return $versions;
1039
-	}
1040
-
1041
-	/**
1042
-	 * find unique extension for restored file if a file with the same name already exists
1043
-	 *
1044
-	 * @param string $location where the file should be restored
1045
-	 * @param string $filename name of the file
1046
-	 * @param View $view filesystem view relative to users root directory
1047
-	 * @return string with unique extension
1048
-	 */
1049
-	private static function getUniqueFilename($location, $filename, View $view) {
1050
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
1051
-		$name = pathinfo($filename, PATHINFO_FILENAME);
1052
-		$l = Util::getL10N('files_trashbin');
1053
-
1054
-		$location = '/' . trim($location, '/');
1055
-
1056
-		// if extension is not empty we set a dot in front of it
1057
-		if ($ext !== '') {
1058
-			$ext = '.' . $ext;
1059
-		}
1060
-
1061
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1062
-			$i = 2;
1063
-			$uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1064
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1065
-				$uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1066
-				$i++;
1067
-			}
1068
-
1069
-			return $uniqueName;
1070
-		}
1071
-
1072
-		return $filename;
1073
-	}
1074
-
1075
-	/**
1076
-	 * get the size from a given root folder
1077
-	 *
1078
-	 * @param View $view file view on the root folder
1079
-	 * @return int|float size of the folder
1080
-	 */
1081
-	private static function calculateSize(View $view): int|float {
1082
-		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1083
-		if (!file_exists($root)) {
1084
-			return 0;
1085
-		}
1086
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1087
-		$size = 0;
1088
-
1089
-		/**
1090
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1091
-		 * This bug is fixed in PHP 5.5.9 or before
1092
-		 * See #8376
1093
-		 */
1094
-		$iterator->rewind();
1095
-		while ($iterator->valid()) {
1096
-			$path = $iterator->current();
1097
-			$relpath = substr($path, strlen($root) - 1);
1098
-			if (!$view->is_dir($relpath)) {
1099
-				$size += $view->filesize($relpath);
1100
-			}
1101
-			$iterator->next();
1102
-		}
1103
-		return $size;
1104
-	}
1105
-
1106
-	/**
1107
-	 * get current size of trash bin from a given user
1108
-	 *
1109
-	 * @param string $user user who owns the trash bin
1110
-	 * @return int|float trash bin size
1111
-	 */
1112
-	private static function getTrashbinSize(string $user): int|float {
1113
-		$view = new View('/' . $user);
1114
-		$fileInfo = $view->getFileInfo('/files_trashbin');
1115
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1116
-	}
1117
-
1118
-	/**
1119
-	 * check if trash bin is empty for a given user
1120
-	 *
1121
-	 * @param string $user
1122
-	 * @return bool
1123
-	 */
1124
-	public static function isEmpty($user) {
1125
-		$view = new View('/' . $user . '/files_trashbin');
1126
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1127
-			while (($file = readdir($dh)) !== false) {
1128
-				if (!Filesystem::isIgnoredDir($file)) {
1129
-					return false;
1130
-				}
1131
-			}
1132
-		}
1133
-		return true;
1134
-	}
1135
-
1136
-	/**
1137
-	 * @param $path
1138
-	 * @return string
1139
-	 */
1140
-	public static function preview_icon($path) {
1141
-		return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1142
-	}
1143
-
1144
-	/**
1145
-	 * Return the filename used in the trash bin
1146
-	 */
1147
-	public static function getTrashFilename(string $filename, int $timestamp): string {
1148
-		$trashFilename = $filename . '.d' . $timestamp;
1149
-		$length = strlen($trashFilename);
1150
-		// oc_filecache `name` column has a limit of 250 chars
1151
-		$maxLength = 250;
1152
-		if ($length > $maxLength) {
1153
-			$trashFilename = substr_replace(
1154
-				$trashFilename,
1155
-				'',
1156
-				$maxLength / 2,
1157
-				$length - $maxLength
1158
-			);
1159
-		}
1160
-		return $trashFilename;
1161
-	}
1162
-
1163
-	private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node {
1164
-		$rootFolder = Server::get(IRootFolder::class);
1165
-		$path = ltrim($path, '/');
1166
-
1167
-		$userFolder = $rootFolder->getUserFolder($user);
1168
-		/** @var Folder $trashFolder */
1169
-		$trashFolder = $userFolder->getParent()->get($baseDir);
1170
-		try {
1171
-			return $trashFolder->get($path);
1172
-		} catch (NotFoundException $ex) {
1173
-		}
1174
-
1175
-		$view = Server::get(View::class);
1176
-		$fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1177
-
1178
-		if (Filesystem::is_dir($path)) {
1179
-			return new NonExistingFolder($rootFolder, $view, $fullPath);
1180
-		} else {
1181
-			return new NonExistingFile($rootFolder, $view, $fullPath);
1182
-		}
1183
-	}
1184
-
1185
-	public function handle(Event $event): void {
1186
-		if ($event instanceof BeforeNodeDeletedEvent) {
1187
-			self::ensureFileScannedHook($event->getNode());
1188
-		}
1189
-	}
53
+    // unit: percentage; 50% of available disk space/quota
54
+    public const DEFAULTMAXSIZE = 50;
55
+
56
+    /**
57
+     * Ensure we don't need to scan the file during the move to trash
58
+     * by triggering the scan in the pre-hook
59
+     */
60
+    public static function ensureFileScannedHook(Node $node): void {
61
+        try {
62
+            self::getUidAndFilename($node->getPath());
63
+        } catch (NotFoundException $e) {
64
+            // Nothing to scan for non existing files
65
+        }
66
+    }
67
+
68
+    /**
69
+     * get the UID of the owner of the file and the path to the file relative to
70
+     * owners files folder
71
+     *
72
+     * @param string $filename
73
+     * @return array
74
+     * @throws NoUserException
75
+     */
76
+    public static function getUidAndFilename($filename) {
77
+        $uid = Filesystem::getOwner($filename);
78
+        $userManager = Server::get(IUserManager::class);
79
+        // if the user with the UID doesn't exists, e.g. because the UID points
80
+        // to a remote user with a federated cloud ID we use the current logged-in
81
+        // user. We need a valid local user to move the file to the right trash bin
82
+        if (!$userManager->userExists($uid)) {
83
+            $uid = OC_User::getUser();
84
+        }
85
+        if (!$uid) {
86
+            // no owner, usually because of share link from ext storage
87
+            return [null, null];
88
+        }
89
+        Filesystem::initMountPoints($uid);
90
+        if ($uid !== OC_User::getUser()) {
91
+            $info = Filesystem::getFileInfo($filename);
92
+            $ownerView = new View('/' . $uid . '/files');
93
+            try {
94
+                $filename = $ownerView->getPath($info['fileid']);
95
+            } catch (NotFoundException $e) {
96
+                $filename = null;
97
+            }
98
+        }
99
+        return [$uid, $filename];
100
+    }
101
+
102
+    /**
103
+     * get original location and deleted by of files for user
104
+     *
105
+     * @param string $user
106
+     * @return array<string, array<string, array{location: string, deletedBy: string}>>
107
+     */
108
+    public static function getExtraData($user) {
109
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
110
+        $query->select('id', 'timestamp', 'location', 'deleted_by')
111
+            ->from('files_trash')
112
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
113
+        $result = $query->executeQuery();
114
+        $array = [];
115
+        while ($row = $result->fetch()) {
116
+            $array[$row['id']][$row['timestamp']] = [
117
+                'location' => (string)$row['location'],
118
+                'deletedBy' => (string)$row['deleted_by'],
119
+            ];
120
+        }
121
+        $result->closeCursor();
122
+        return $array;
123
+    }
124
+
125
+    /**
126
+     * get original location of file
127
+     *
128
+     * @param string $user
129
+     * @param string $filename
130
+     * @param string $timestamp
131
+     * @return string|false original location
132
+     */
133
+    public static function getLocation($user, $filename, $timestamp) {
134
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
135
+        $query->select('location')
136
+            ->from('files_trash')
137
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
138
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
139
+            ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
140
+
141
+        $result = $query->executeQuery();
142
+        $row = $result->fetch();
143
+        $result->closeCursor();
144
+
145
+        if (isset($row['location'])) {
146
+            return $row['location'];
147
+        } else {
148
+            return false;
149
+        }
150
+    }
151
+
152
+    /** @param string $user */
153
+    private static function setUpTrash($user): void {
154
+        $view = new View('/' . $user);
155
+        if (!$view->is_dir('files_trashbin')) {
156
+            $view->mkdir('files_trashbin');
157
+        }
158
+        if (!$view->is_dir('files_trashbin/files')) {
159
+            $view->mkdir('files_trashbin/files');
160
+        }
161
+        if (!$view->is_dir('files_trashbin/versions')) {
162
+            $view->mkdir('files_trashbin/versions');
163
+        }
164
+        if (!$view->is_dir('files_trashbin/keys')) {
165
+            $view->mkdir('files_trashbin/keys');
166
+        }
167
+    }
168
+
169
+
170
+    /**
171
+     * copy file to owners trash
172
+     *
173
+     * @param string $sourcePath
174
+     * @param string $owner
175
+     * @param string $targetPath
176
+     * @param string $user
177
+     * @param int $timestamp
178
+     */
179
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp): void {
180
+        self::setUpTrash($owner);
181
+
182
+        $targetFilename = basename($targetPath);
183
+        $targetLocation = dirname($targetPath);
184
+
185
+        $sourceFilename = basename($sourcePath);
186
+
187
+        $view = new View('/');
188
+
189
+        $target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
190
+        $source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
191
+        $free = $view->free_space($target);
192
+        $isUnknownOrUnlimitedFreeSpace = $free < 0;
193
+        $isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
194
+        if ($isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft) {
195
+            self::copy_recursive($source, $target, $view);
196
+        }
197
+
198
+
199
+        if ($view->file_exists($target)) {
200
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
201
+            $query->insert('files_trash')
202
+                ->setValue('id', $query->createNamedParameter($targetFilename))
203
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
204
+                ->setValue('location', $query->createNamedParameter($targetLocation))
205
+                ->setValue('user', $query->createNamedParameter($user))
206
+                ->setValue('deleted_by', $query->createNamedParameter($user));
207
+            $result = $query->executeStatement();
208
+            if (!$result) {
209
+                Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
210
+            }
211
+        }
212
+    }
213
+
214
+
215
+    /**
216
+     * move file to the trash bin
217
+     *
218
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
219
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
220
+     *
221
+     * @return bool
222
+     */
223
+    public static function move2trash($file_path, $ownerOnly = false) {
224
+        // get the user for which the filesystem is setup
225
+        $root = Filesystem::getRoot();
226
+        [, $user] = explode('/', $root);
227
+        [$owner, $ownerPath] = self::getUidAndFilename($file_path);
228
+
229
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
230
+        if (is_null($owner)) {
231
+            $owner = $user;
232
+            $ownerPath = $file_path;
233
+        }
234
+
235
+        $ownerView = new View('/' . $owner);
236
+
237
+        // file has been deleted in between
238
+        if (is_null($ownerPath) || $ownerPath === '') {
239
+            return true;
240
+        }
241
+
242
+        $sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
243
+
244
+        if ($sourceInfo === false) {
245
+            return true;
246
+        }
247
+
248
+        self::setUpTrash($user);
249
+        if ($owner !== $user) {
250
+            // also setup for owner
251
+            self::setUpTrash($owner);
252
+        }
253
+
254
+        $path_parts = pathinfo($ownerPath);
255
+
256
+        $filename = $path_parts['basename'];
257
+        $location = $path_parts['dirname'];
258
+        /** @var ITimeFactory $timeFactory */
259
+        $timeFactory = Server::get(ITimeFactory::class);
260
+        $timestamp = $timeFactory->getTime();
261
+
262
+        $lockingProvider = Server::get(ILockingProvider::class);
263
+
264
+        // disable proxy to prevent recursive calls
265
+        $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
266
+        $gotLock = false;
267
+
268
+        do {
269
+            /** @var ILockingStorage & IStorage $trashStorage */
270
+            [$trashStorage, $trashInternalPath] = $ownerView->resolvePath($trashPath);
271
+            try {
272
+                $trashStorage->acquireLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
273
+                $gotLock = true;
274
+            } catch (LockedException $e) {
275
+                // a file with the same name is being deleted concurrently
276
+                // nudge the timestamp a bit to resolve the conflict
277
+
278
+                $timestamp = $timestamp + 1;
279
+
280
+                $trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
281
+            }
282
+        } while (!$gotLock);
283
+
284
+        $sourceStorage = $sourceInfo->getStorage();
285
+        $sourceInternalPath = $sourceInfo->getInternalPath();
286
+
287
+        if ($trashStorage->file_exists($trashInternalPath)) {
288
+            $trashStorage->unlink($trashInternalPath);
289
+        }
290
+
291
+        $configuredTrashbinSize = static::getConfiguredTrashbinSize($owner);
292
+        if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
293
+            return false;
294
+        }
295
+
296
+        try {
297
+            $moveSuccessful = true;
298
+
299
+            $inCache = $sourceStorage->getCache()->inCache($sourceInternalPath);
300
+            $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
301
+            if ($inCache) {
302
+                $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
303
+            }
304
+        } catch (CopyRecursiveException $e) {
305
+            $moveSuccessful = false;
306
+            if ($trashStorage->file_exists($trashInternalPath)) {
307
+                $trashStorage->unlink($trashInternalPath);
308
+            }
309
+            Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
310
+        }
311
+
312
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
313
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
314
+                $sourceStorage->rmdir($sourceInternalPath);
315
+            } else {
316
+                $sourceStorage->unlink($sourceInternalPath);
317
+            }
318
+
319
+            if ($sourceStorage->file_exists($sourceInternalPath)) {
320
+                // undo the cache move
321
+                $sourceStorage->getUpdater()->renameFromStorage($trashStorage, $trashInternalPath, $sourceInternalPath);
322
+            } else {
323
+                $trashStorage->getUpdater()->remove($trashInternalPath);
324
+            }
325
+            return false;
326
+        }
327
+
328
+        if ($moveSuccessful) {
329
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
330
+            $query->insert('files_trash')
331
+                ->setValue('id', $query->createNamedParameter($filename))
332
+                ->setValue('timestamp', $query->createNamedParameter($timestamp))
333
+                ->setValue('location', $query->createNamedParameter($location))
334
+                ->setValue('user', $query->createNamedParameter($owner))
335
+                ->setValue('deleted_by', $query->createNamedParameter($user));
336
+            $result = $query->executeStatement();
337
+            if (!$result) {
338
+                Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
339
+            }
340
+            Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
341
+                'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
342
+
343
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
344
+
345
+            // if owner !== user we need to also add a copy to the users trash
346
+            if ($user !== $owner && $ownerOnly === false) {
347
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
348
+            }
349
+        }
350
+
351
+        $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
352
+
353
+        self::scheduleExpire($user);
354
+
355
+        // if owner !== user we also need to update the owners trash size
356
+        if ($owner !== $user) {
357
+            self::scheduleExpire($owner);
358
+        }
359
+
360
+        return $moveSuccessful;
361
+    }
362
+
363
+    private static function getConfiguredTrashbinSize(string $user): int|float {
364
+        $config = Server::get(IConfig::class);
365
+        $userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
366
+        if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
367
+            return Util::numericToNumber($userTrashbinSize);
368
+        }
369
+        $systemTrashbinSize = $config->getAppValue('files_trashbin', 'trashbin_size', '-1');
370
+        if (is_numeric($systemTrashbinSize)) {
371
+            return Util::numericToNumber($systemTrashbinSize);
372
+        }
373
+        return -1;
374
+    }
375
+
376
+    /**
377
+     * Move file versions to trash so that they can be restored later
378
+     *
379
+     * @param string $filename of deleted file
380
+     * @param string $owner owner user id
381
+     * @param string $ownerPath path relative to the owner's home storage
382
+     * @param int $timestamp when the file was deleted
383
+     */
384
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
385
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions') && !empty($ownerPath)) {
386
+            $user = OC_User::getUser();
387
+            $rootView = new View('/');
388
+
389
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
390
+                if ($owner !== $user) {
391
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
392
+                }
393
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
394
+            } elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
395
+                foreach ($versions as $v) {
396
+                    if ($owner !== $user) {
397
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
398
+                    }
399
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
400
+                }
401
+            }
402
+        }
403
+    }
404
+
405
+    /**
406
+     * Move a file or folder on storage level
407
+     *
408
+     * @param View $view
409
+     * @param string $source
410
+     * @param string $target
411
+     * @return bool
412
+     */
413
+    private static function move(View $view, $source, $target) {
414
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
415
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
416
+        /** @var \OC\Files\Storage\Storage $targetStorage */
417
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
418
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
419
+
420
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
421
+        if ($result) {
422
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
423
+        }
424
+        return $result;
425
+    }
426
+
427
+    /**
428
+     * Copy a file or folder on storage level
429
+     *
430
+     * @param View $view
431
+     * @param string $source
432
+     * @param string $target
433
+     * @return bool
434
+     */
435
+    private static function copy(View $view, $source, $target) {
436
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
437
+        [$sourceStorage, $sourceInternalPath] = $view->resolvePath($source);
438
+        /** @var \OC\Files\Storage\Storage $targetStorage */
439
+        [$targetStorage, $targetInternalPath] = $view->resolvePath($target);
440
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
441
+
442
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
443
+        if ($result) {
444
+            $targetStorage->getUpdater()->update($targetInternalPath);
445
+        }
446
+        return $result;
447
+    }
448
+
449
+    /**
450
+     * Restore a file or folder from trash bin
451
+     *
452
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
453
+     *                     including the timestamp suffix ".d12345678"
454
+     * @param string $filename name of the file/folder
455
+     * @param int $timestamp time when the file/folder was deleted
456
+     *
457
+     * @return bool true on success, false otherwise
458
+     */
459
+    public static function restore($file, $filename, $timestamp) {
460
+        $user = OC_User::getUser();
461
+        if (!$user) {
462
+            throw new \Exception('Tried to restore a file while not logged in');
463
+        }
464
+        $view = new View('/' . $user);
465
+
466
+        $location = '';
467
+        if ($timestamp) {
468
+            $location = self::getLocation($user, $filename, $timestamp);
469
+            if ($location === false) {
470
+                Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
471
+            } else {
472
+                // if location no longer exists, restore file in the root directory
473
+                if ($location !== '/'
474
+                    && (!$view->is_dir('files/' . $location)
475
+                        || !$view->isCreatable('files/' . $location))
476
+                ) {
477
+                    $location = '';
478
+                }
479
+            }
480
+        }
481
+
482
+        // we need a  extension in case a file/dir with the same name already exists
483
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
484
+
485
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
486
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
487
+        if (!$view->file_exists($source)) {
488
+            return false;
489
+        }
490
+        $mtime = $view->filemtime($source);
491
+
492
+        // restore file
493
+        if (!$view->isCreatable(dirname($target))) {
494
+            throw new NotPermittedException("Can't restore trash item because the target folder is not writable");
495
+        }
496
+
497
+        $sourcePath = Filesystem::normalizePath($file);
498
+        $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
499
+
500
+        $sourceNode = self::getNodeForPath($user, $sourcePath);
501
+        $targetNode = self::getNodeForPath($user, $targetPath, 'files');
502
+        $run = true;
503
+        $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run);
504
+        $dispatcher = Server::get(IEventDispatcher::class);
505
+        $dispatcher->dispatchTyped($event);
506
+
507
+        if (!$run) {
508
+            return false;
509
+        }
510
+
511
+        $restoreResult = $view->rename($source, $target);
512
+
513
+        // handle the restore result
514
+        if ($restoreResult) {
515
+            $fakeRoot = $view->getRoot();
516
+            $view->chroot('/' . $user . '/files');
517
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
518
+            $view->chroot($fakeRoot);
519
+            Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
520
+
521
+            $sourceNode = self::getNodeForPath($user, $sourcePath);
522
+            $targetNode = self::getNodeForPath($user, $targetPath, 'files');
523
+            $event = new NodeRestoredEvent($sourceNode, $targetNode);
524
+            $dispatcher = Server::get(IEventDispatcher::class);
525
+            $dispatcher->dispatchTyped($event);
526
+
527
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
528
+
529
+            if ($timestamp) {
530
+                $query = Server::get(IDBConnection::class)->getQueryBuilder();
531
+                $query->delete('files_trash')
532
+                    ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
533
+                    ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
534
+                    ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
535
+                $query->executeStatement();
536
+            }
537
+
538
+            return true;
539
+        }
540
+
541
+        return false;
542
+    }
543
+
544
+    /**
545
+     * restore versions from trash bin
546
+     *
547
+     * @param View $view file view
548
+     * @param string $file complete path to file
549
+     * @param string $filename name of file once it was deleted
550
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
551
+     * @param string $location location if file
552
+     * @param int $timestamp deletion time
553
+     * @return false|null
554
+     */
555
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
556
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
557
+            $user = OC_User::getUser();
558
+            $rootView = new View('/');
559
+
560
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
561
+
562
+            [$owner, $ownerPath] = self::getUidAndFilename($target);
563
+
564
+            // file has been deleted in between
565
+            if (empty($ownerPath)) {
566
+                return false;
567
+            }
568
+
569
+            if ($timestamp) {
570
+                $versionedFile = $filename;
571
+            } else {
572
+                $versionedFile = $file;
573
+            }
574
+
575
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
576
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
577
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
578
+                foreach ($versions as $v) {
579
+                    if ($timestamp) {
580
+                        $rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
581
+                    } else {
582
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
583
+                    }
584
+                }
585
+            }
586
+        }
587
+    }
588
+
589
+    /**
590
+     * delete all files from the trash
591
+     */
592
+    public static function deleteAll() {
593
+        $user = OC_User::getUser();
594
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
595
+        $view = new View('/' . $user);
596
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
597
+
598
+        try {
599
+            $trash = $userRoot->get('files_trashbin');
600
+        } catch (NotFoundException $e) {
601
+            return false;
602
+        }
603
+
604
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
605
+        $filePaths = [];
606
+        foreach ($fileInfos as $fileInfo) {
607
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
608
+        }
609
+        unset($fileInfos); // save memory
610
+
611
+        // Bulk PreDelete-Hook
612
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
613
+
614
+        // Single-File Hooks
615
+        foreach ($filePaths as $path) {
616
+            self::emitTrashbinPreDelete($path);
617
+        }
618
+
619
+        // actual file deletion
620
+        $trash->delete();
621
+
622
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
623
+        $query->delete('files_trash')
624
+            ->where($query->expr()->eq('user', $query->createNamedParameter($user)));
625
+        $query->executeStatement();
626
+
627
+        // Bulk PostDelete-Hook
628
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
629
+
630
+        // Single-File Hooks
631
+        foreach ($filePaths as $path) {
632
+            self::emitTrashbinPostDelete($path);
633
+        }
634
+
635
+        $trash = $userRoot->newFolder('files_trashbin');
636
+        $trash->newFolder('files');
637
+
638
+        return true;
639
+    }
640
+
641
+    /**
642
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
643
+     *
644
+     * @param string $path
645
+     */
646
+    protected static function emitTrashbinPreDelete($path) {
647
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
648
+    }
649
+
650
+    /**
651
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
652
+     *
653
+     * @param string $path
654
+     */
655
+    protected static function emitTrashbinPostDelete($path) {
656
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
657
+    }
658
+
659
+    /**
660
+     * delete file from trash bin permanently
661
+     *
662
+     * @param string $filename path to the file
663
+     * @param string $user
664
+     * @param int $timestamp of deletion time
665
+     *
666
+     * @return int|float size of deleted files
667
+     */
668
+    public static function delete($filename, $user, $timestamp = null) {
669
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
670
+        $view = new View('/' . $user);
671
+        $size = 0;
672
+
673
+        if ($timestamp) {
674
+            $query = Server::get(IDBConnection::class)->getQueryBuilder();
675
+            $query->delete('files_trash')
676
+                ->where($query->expr()->eq('user', $query->createNamedParameter($user)))
677
+                ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
678
+                ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
679
+            $query->executeStatement();
680
+
681
+            $file = static::getTrashFilename($filename, $timestamp);
682
+        } else {
683
+            $file = $filename;
684
+        }
685
+
686
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
687
+
688
+        try {
689
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
690
+        } catch (NotFoundException $e) {
691
+            return $size;
692
+        }
693
+
694
+        if ($node instanceof Folder) {
695
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
696
+        } elseif ($node instanceof File) {
697
+            $size += $view->filesize('/files_trashbin/files/' . $file);
698
+        }
699
+
700
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
701
+        $node->delete();
702
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
703
+
704
+        return $size;
705
+    }
706
+
707
+    /**
708
+     * @param string $file
709
+     * @param string $filename
710
+     * @param ?int $timestamp
711
+     */
712
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
713
+        $size = 0;
714
+        if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
715
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
716
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
717
+                $view->unlink('files_trashbin/versions/' . $file);
718
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
719
+                foreach ($versions as $v) {
720
+                    if ($timestamp) {
721
+                        $size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
722
+                        $view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
723
+                    } else {
724
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
725
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
726
+                    }
727
+                }
728
+            }
729
+        }
730
+        return $size;
731
+    }
732
+
733
+    /**
734
+     * check to see whether a file exists in trashbin
735
+     *
736
+     * @param string $filename path to the file
737
+     * @param int $timestamp of deletion time
738
+     * @return bool true if file exists, otherwise false
739
+     */
740
+    public static function file_exists($filename, $timestamp = null) {
741
+        $user = OC_User::getUser();
742
+        $view = new View('/' . $user);
743
+
744
+        if ($timestamp) {
745
+            $filename = static::getTrashFilename($filename, $timestamp);
746
+        }
747
+
748
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
749
+        return $view->file_exists($target);
750
+    }
751
+
752
+    /**
753
+     * deletes used space for trash bin in db if user was deleted
754
+     *
755
+     * @param string $uid id of deleted user
756
+     * @return bool result of db delete operation
757
+     */
758
+    public static function deleteUser($uid) {
759
+        $query = Server::get(IDBConnection::class)->getQueryBuilder();
760
+        $query->delete('files_trash')
761
+            ->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
762
+        return (bool)$query->executeStatement();
763
+    }
764
+
765
+    /**
766
+     * calculate remaining free space for trash bin
767
+     *
768
+     * @param int|float $trashbinSize current size of the trash bin
769
+     * @param string $user
770
+     * @return int|float available free space for trash bin
771
+     */
772
+    private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
773
+        $configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
774
+        if ($configuredTrashbinSize > -1) {
775
+            return $configuredTrashbinSize - $trashbinSize;
776
+        }
777
+
778
+        $userObject = Server::get(IUserManager::class)->get($user);
779
+        if (is_null($userObject)) {
780
+            return 0;
781
+        }
782
+        $softQuota = true;
783
+        $quota = $userObject->getQuota();
784
+        if ($quota === null || $quota === 'none') {
785
+            $quota = Filesystem::free_space('/');
786
+            $softQuota = false;
787
+            // inf or unknown free space
788
+            if ($quota < 0) {
789
+                $quota = PHP_INT_MAX;
790
+            }
791
+        } else {
792
+            $quota = Util::computerFileSize($quota);
793
+            // invalid quota
794
+            if ($quota === false) {
795
+                $quota = PHP_INT_MAX;
796
+            }
797
+        }
798
+
799
+        // calculate available space for trash bin
800
+        // subtract size of files and current trash bin size from quota
801
+        if ($softQuota) {
802
+            $userFolder = \OC::$server->getUserFolder($user);
803
+            if (is_null($userFolder)) {
804
+                return 0;
805
+            }
806
+            $free = $quota - $userFolder->getSize(false); // remaining free space for user
807
+            if ($free > 0) {
808
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
809
+            } else {
810
+                $availableSpace = $free - $trashbinSize;
811
+            }
812
+        } else {
813
+            $availableSpace = $quota;
814
+        }
815
+
816
+        return Util::numericToNumber($availableSpace);
817
+    }
818
+
819
+    /**
820
+     * resize trash bin if necessary after a new file was added to Nextcloud
821
+     *
822
+     * @param string $user user id
823
+     */
824
+    public static function resizeTrash($user) {
825
+        $size = self::getTrashbinSize($user);
826
+
827
+        $freeSpace = self::calculateFreeSpace($size, $user);
828
+
829
+        if ($freeSpace < 0) {
830
+            self::scheduleExpire($user);
831
+        }
832
+    }
833
+
834
+    /**
835
+     * clean up the trash bin
836
+     *
837
+     * @param string $user
838
+     */
839
+    public static function expire($user) {
840
+        $trashBinSize = self::getTrashbinSize($user);
841
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
842
+
843
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
844
+
845
+        // delete all files older then $retention_obligation
846
+        [$delSize, $count] = self::deleteExpiredFiles($dirContent, $user);
847
+
848
+        $availableSpace += $delSize;
849
+
850
+        // delete files from trash until we meet the trash bin size limit again
851
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
852
+    }
853
+
854
+    /**
855
+     * @param string $user
856
+     */
857
+    private static function scheduleExpire($user) {
858
+        // let the admin disable auto expire
859
+        $expiration = Server::get(Expiration::class);
860
+        if ($expiration->isEnabled()) {
861
+            Server::get(IBus::class)->push(new Expire($user));
862
+        }
863
+    }
864
+
865
+    /**
866
+     * if the size limit for the trash bin is reached, we delete the oldest
867
+     * files in the trash bin until we meet the limit again
868
+     *
869
+     * @param array $files
870
+     * @param string $user
871
+     * @param int|float $availableSpace available disc space
872
+     * @return int|float size of deleted files
873
+     */
874
+    protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
875
+        $expiration = Server::get(Expiration::class);
876
+        $size = 0;
877
+
878
+        if ($availableSpace <= 0) {
879
+            foreach ($files as $file) {
880
+                if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
881
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
882
+                    Server::get(LoggerInterface::class)->info(
883
+                        'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
884
+                        [
885
+                            'app' => 'files_trashbin',
886
+                            'user' => $user,
887
+                        ]
888
+                    );
889
+                    $availableSpace += $tmp;
890
+                    $size += $tmp;
891
+                } else {
892
+                    break;
893
+                }
894
+            }
895
+        }
896
+        return $size;
897
+    }
898
+
899
+    /**
900
+     * delete files older then max storage time
901
+     *
902
+     * @param array $files list of files sorted by mtime
903
+     * @param string $user
904
+     * @return array{int|float, int} size of deleted files and number of deleted files
905
+     */
906
+    public static function deleteExpiredFiles($files, $user) {
907
+        $expiration = Server::get(Expiration::class);
908
+        $size = 0;
909
+        $count = 0;
910
+        foreach ($files as $file) {
911
+            $timestamp = $file['mtime'];
912
+            $filename = $file['name'];
913
+            if ($expiration->isExpired($timestamp)) {
914
+                try {
915
+                    $size += self::delete($filename, $user, $timestamp);
916
+                    $count++;
917
+                } catch (NotPermittedException $e) {
918
+                    Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
919
+                        [
920
+                            'exception' => $e,
921
+                            'app' => 'files_trashbin',
922
+                            'user' => $user,
923
+                        ]
924
+                    );
925
+                }
926
+                Server::get(LoggerInterface::class)->info(
927
+                    'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
928
+                    [
929
+                        'app' => 'files_trashbin',
930
+                        'user' => $user,
931
+                    ],
932
+                );
933
+            } else {
934
+                break;
935
+            }
936
+        }
937
+
938
+        return [$size, $count];
939
+    }
940
+
941
+    /**
942
+     * recursive copy to copy a whole directory
943
+     *
944
+     * @param string $source source path, relative to the users files directory
945
+     * @param string $destination destination path relative to the users root directory
946
+     * @param View $view file view for the users root directory
947
+     * @return int|float
948
+     * @throws Exceptions\CopyRecursiveException
949
+     */
950
+    private static function copy_recursive($source, $destination, View $view): int|float {
951
+        $size = 0;
952
+        if ($view->is_dir($source)) {
953
+            $view->mkdir($destination);
954
+            $view->touch($destination, $view->filemtime($source));
955
+            foreach ($view->getDirectoryContent($source) as $i) {
956
+                $pathDir = $source . '/' . $i['name'];
957
+                if ($view->is_dir($pathDir)) {
958
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
959
+                } else {
960
+                    $size += $view->filesize($pathDir);
961
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
962
+                    if (!$result) {
963
+                        throw new CopyRecursiveException();
964
+                    }
965
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
966
+                }
967
+            }
968
+        } else {
969
+            $size += $view->filesize($source);
970
+            $result = $view->copy($source, $destination);
971
+            if (!$result) {
972
+                throw new CopyRecursiveException();
973
+            }
974
+            $view->touch($destination, $view->filemtime($source));
975
+        }
976
+        return $size;
977
+    }
978
+
979
+    /**
980
+     * find all versions which belong to the file we want to restore
981
+     *
982
+     * @param string $filename name of the file which should be restored
983
+     * @param int $timestamp timestamp when the file was deleted
984
+     */
985
+    private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
986
+        $view = new View('/' . $user . '/files_trashbin/versions');
987
+        $versions = [];
988
+
989
+        /** @var \OC\Files\Storage\Storage $storage */
990
+        [$storage,] = $view->resolvePath('/');
991
+
992
+        $pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
993
+        if ($timestamp) {
994
+            // fetch for old versions
995
+            $escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
996
+            $pattern .= '.v%.d' . $escapedTimestamp;
997
+            $offset = -strlen($escapedTimestamp) - 2;
998
+        } else {
999
+            $pattern .= '.v%';
1000
+        }
1001
+
1002
+        // Manually fetch all versions from the file cache to be able to filter them by their parent
1003
+        $cache = $storage->getCache('');
1004
+        $query = new CacheQueryBuilder(
1005
+            Server::get(IDBConnection::class)->getQueryBuilder(),
1006
+            Server::get(IFilesMetadataManager::class),
1007
+        );
1008
+        $normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1009
+        $parentId = $cache->getId($normalizedParentPath);
1010
+        if ($parentId === -1) {
1011
+            return [];
1012
+        }
1013
+
1014
+        $query->selectFileCache()
1015
+            ->whereStorageId($cache->getNumericStorageId())
1016
+            ->andWhere($query->expr()->eq('parent', $query->createNamedParameter($parentId)))
1017
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
1018
+
1019
+        $result = $query->executeQuery();
1020
+        $entries = $result->fetchAll();
1021
+        $result->closeCursor();
1022
+
1023
+        /** @var CacheEntry[] $matches */
1024
+        $matches = array_map(function (array $data) {
1025
+            return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1026
+        }, $entries);
1027
+
1028
+        foreach ($matches as $ma) {
1029
+            if ($timestamp) {
1030
+                $parts = explode('.v', substr($ma['path'], 0, $offset));
1031
+                $versions[] = end($parts);
1032
+            } else {
1033
+                $parts = explode('.v', $ma['path']);
1034
+                $versions[] = end($parts);
1035
+            }
1036
+        }
1037
+
1038
+        return $versions;
1039
+    }
1040
+
1041
+    /**
1042
+     * find unique extension for restored file if a file with the same name already exists
1043
+     *
1044
+     * @param string $location where the file should be restored
1045
+     * @param string $filename name of the file
1046
+     * @param View $view filesystem view relative to users root directory
1047
+     * @return string with unique extension
1048
+     */
1049
+    private static function getUniqueFilename($location, $filename, View $view) {
1050
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
1051
+        $name = pathinfo($filename, PATHINFO_FILENAME);
1052
+        $l = Util::getL10N('files_trashbin');
1053
+
1054
+        $location = '/' . trim($location, '/');
1055
+
1056
+        // if extension is not empty we set a dot in front of it
1057
+        if ($ext !== '') {
1058
+            $ext = '.' . $ext;
1059
+        }
1060
+
1061
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
1062
+            $i = 2;
1063
+            $uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1064
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1065
+                $uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1066
+                $i++;
1067
+            }
1068
+
1069
+            return $uniqueName;
1070
+        }
1071
+
1072
+        return $filename;
1073
+    }
1074
+
1075
+    /**
1076
+     * get the size from a given root folder
1077
+     *
1078
+     * @param View $view file view on the root folder
1079
+     * @return int|float size of the folder
1080
+     */
1081
+    private static function calculateSize(View $view): int|float {
1082
+        $root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1083
+        if (!file_exists($root)) {
1084
+            return 0;
1085
+        }
1086
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
1087
+        $size = 0;
1088
+
1089
+        /**
1090
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
1091
+         * This bug is fixed in PHP 5.5.9 or before
1092
+         * See #8376
1093
+         */
1094
+        $iterator->rewind();
1095
+        while ($iterator->valid()) {
1096
+            $path = $iterator->current();
1097
+            $relpath = substr($path, strlen($root) - 1);
1098
+            if (!$view->is_dir($relpath)) {
1099
+                $size += $view->filesize($relpath);
1100
+            }
1101
+            $iterator->next();
1102
+        }
1103
+        return $size;
1104
+    }
1105
+
1106
+    /**
1107
+     * get current size of trash bin from a given user
1108
+     *
1109
+     * @param string $user user who owns the trash bin
1110
+     * @return int|float trash bin size
1111
+     */
1112
+    private static function getTrashbinSize(string $user): int|float {
1113
+        $view = new View('/' . $user);
1114
+        $fileInfo = $view->getFileInfo('/files_trashbin');
1115
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1116
+    }
1117
+
1118
+    /**
1119
+     * check if trash bin is empty for a given user
1120
+     *
1121
+     * @param string $user
1122
+     * @return bool
1123
+     */
1124
+    public static function isEmpty($user) {
1125
+        $view = new View('/' . $user . '/files_trashbin');
1126
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1127
+            while (($file = readdir($dh)) !== false) {
1128
+                if (!Filesystem::isIgnoredDir($file)) {
1129
+                    return false;
1130
+                }
1131
+            }
1132
+        }
1133
+        return true;
1134
+    }
1135
+
1136
+    /**
1137
+     * @param $path
1138
+     * @return string
1139
+     */
1140
+    public static function preview_icon($path) {
1141
+        return Server::get(IURLGenerator::class)->linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1142
+    }
1143
+
1144
+    /**
1145
+     * Return the filename used in the trash bin
1146
+     */
1147
+    public static function getTrashFilename(string $filename, int $timestamp): string {
1148
+        $trashFilename = $filename . '.d' . $timestamp;
1149
+        $length = strlen($trashFilename);
1150
+        // oc_filecache `name` column has a limit of 250 chars
1151
+        $maxLength = 250;
1152
+        if ($length > $maxLength) {
1153
+            $trashFilename = substr_replace(
1154
+                $trashFilename,
1155
+                '',
1156
+                $maxLength / 2,
1157
+                $length - $maxLength
1158
+            );
1159
+        }
1160
+        return $trashFilename;
1161
+    }
1162
+
1163
+    private static function getNodeForPath(string $user, string $path, string $baseDir = 'files_trashbin/files'): Node {
1164
+        $rootFolder = Server::get(IRootFolder::class);
1165
+        $path = ltrim($path, '/');
1166
+
1167
+        $userFolder = $rootFolder->getUserFolder($user);
1168
+        /** @var Folder $trashFolder */
1169
+        $trashFolder = $userFolder->getParent()->get($baseDir);
1170
+        try {
1171
+            return $trashFolder->get($path);
1172
+        } catch (NotFoundException $ex) {
1173
+        }
1174
+
1175
+        $view = Server::get(View::class);
1176
+        $fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1177
+
1178
+        if (Filesystem::is_dir($path)) {
1179
+            return new NonExistingFolder($rootFolder, $view, $fullPath);
1180
+        } else {
1181
+            return new NonExistingFile($rootFolder, $view, $fullPath);
1182
+        }
1183
+    }
1184
+
1185
+    public function handle(Event $event): void {
1186
+        if ($event instanceof BeforeNodeDeletedEvent) {
1187
+            self::ensureFileScannedHook($event->getNode());
1188
+        }
1189
+    }
1190 1190
 }
Please login to merge, or discard this patch.
Spacing   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -89,7 +89,7 @@  discard block
 block discarded – undo
89 89
 		Filesystem::initMountPoints($uid);
90 90
 		if ($uid !== OC_User::getUser()) {
91 91
 			$info = Filesystem::getFileInfo($filename);
92
-			$ownerView = new View('/' . $uid . '/files');
92
+			$ownerView = new View('/'.$uid.'/files');
93 93
 			try {
94 94
 				$filename = $ownerView->getPath($info['fileid']);
95 95
 			} catch (NotFoundException $e) {
@@ -114,8 +114,8 @@  discard block
 block discarded – undo
114 114
 		$array = [];
115 115
 		while ($row = $result->fetch()) {
116 116
 			$array[$row['id']][$row['timestamp']] = [
117
-				'location' => (string)$row['location'],
118
-				'deletedBy' => (string)$row['deleted_by'],
117
+				'location' => (string) $row['location'],
118
+				'deletedBy' => (string) $row['deleted_by'],
119 119
 			];
120 120
 		}
121 121
 		$result->closeCursor();
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
 
152 152
 	/** @param string $user */
153 153
 	private static function setUpTrash($user): void {
154
-		$view = new View('/' . $user);
154
+		$view = new View('/'.$user);
155 155
 		if (!$view->is_dir('files_trashbin')) {
156 156
 			$view->mkdir('files_trashbin');
157 157
 		}
@@ -186,8 +186,8 @@  discard block
 block discarded – undo
186 186
 
187 187
 		$view = new View('/');
188 188
 
189
-		$target = $user . '/files_trashbin/files/' . static::getTrashFilename($targetFilename, $timestamp);
190
-		$source = $owner . '/files_trashbin/files/' . static::getTrashFilename($sourceFilename, $timestamp);
189
+		$target = $user.'/files_trashbin/files/'.static::getTrashFilename($targetFilename, $timestamp);
190
+		$source = $owner.'/files_trashbin/files/'.static::getTrashFilename($sourceFilename, $timestamp);
191 191
 		$free = $view->free_space($target);
192 192
 		$isUnknownOrUnlimitedFreeSpace = $free < 0;
193 193
 		$isEnoughFreeSpaceLeft = $view->filesize($source) < $free;
@@ -232,14 +232,14 @@  discard block
 block discarded – undo
232 232
 			$ownerPath = $file_path;
233 233
 		}
234 234
 
235
-		$ownerView = new View('/' . $owner);
235
+		$ownerView = new View('/'.$owner);
236 236
 
237 237
 		// file has been deleted in between
238 238
 		if (is_null($ownerPath) || $ownerPath === '') {
239 239
 			return true;
240 240
 		}
241 241
 
242
-		$sourceInfo = $ownerView->getFileInfo('/files/' . $ownerPath);
242
+		$sourceInfo = $ownerView->getFileInfo('/files/'.$ownerPath);
243 243
 
244 244
 		if ($sourceInfo === false) {
245 245
 			return true;
@@ -262,7 +262,7 @@  discard block
 block discarded – undo
262 262
 		$lockingProvider = Server::get(ILockingProvider::class);
263 263
 
264 264
 		// disable proxy to prevent recursive calls
265
-		$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
265
+		$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
266 266
 		$gotLock = false;
267 267
 
268 268
 		do {
@@ -277,7 +277,7 @@  discard block
 block discarded – undo
277 277
 
278 278
 				$timestamp = $timestamp + 1;
279 279
 
280
-				$trashPath = '/files_trashbin/files/' . static::getTrashFilename($filename, $timestamp);
280
+				$trashPath = '/files_trashbin/files/'.static::getTrashFilename($filename, $timestamp);
281 281
 			}
282 282
 		} while (!$gotLock);
283 283
 
@@ -306,7 +306,7 @@  discard block
 block discarded – undo
306 306
 			if ($trashStorage->file_exists($trashInternalPath)) {
307 307
 				$trashStorage->unlink($trashInternalPath);
308 308
 			}
309
-			Server::get(LoggerInterface::class)->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
309
+			Server::get(LoggerInterface::class)->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
310 310
 		}
311 311
 
312 312
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -360,7 +360,7 @@  discard block
 block discarded – undo
360 360
 		return $moveSuccessful;
361 361
 	}
362 362
 
363
-	private static function getConfiguredTrashbinSize(string $user): int|float {
363
+	private static function getConfiguredTrashbinSize(string $user): int | float {
364 364
 		$config = Server::get(IConfig::class);
365 365
 		$userTrashbinSize = $config->getUserValue($user, 'files_trashbin', 'trashbin_size', '-1');
366 366
 		if (is_numeric($userTrashbinSize) && ($userTrashbinSize > -1)) {
@@ -386,17 +386,17 @@  discard block
 block discarded – undo
386 386
 			$user = OC_User::getUser();
387 387
 			$rootView = new View('/');
388 388
 
389
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
389
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
390 390
 				if ($owner !== $user) {
391
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
391
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.static::getTrashFilename(basename($ownerPath), $timestamp), $rootView);
392 392
 				}
393
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . static::getTrashFilename($filename, $timestamp));
393
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.static::getTrashFilename($filename, $timestamp));
394 394
 			} elseif ($versions = Storage::getVersions($owner, $ownerPath)) {
395 395
 				foreach ($versions as $v) {
396 396
 					if ($owner !== $user) {
397
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . static::getTrashFilename($v['name'] . '.v' . $v['version'], $timestamp));
397
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.static::getTrashFilename($v['name'].'.v'.$v['version'], $timestamp));
398 398
 					}
399
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v['version'], $timestamp));
399
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v['version'], $timestamp));
400 400
 				}
401 401
 			}
402 402
 		}
@@ -461,18 +461,18 @@  discard block
 block discarded – undo
461 461
 		if (!$user) {
462 462
 			throw new \Exception('Tried to restore a file while not logged in');
463 463
 		}
464
-		$view = new View('/' . $user);
464
+		$view = new View('/'.$user);
465 465
 
466 466
 		$location = '';
467 467
 		if ($timestamp) {
468 468
 			$location = self::getLocation($user, $filename, $timestamp);
469 469
 			if ($location === false) {
470
-				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
470
+				Server::get(LoggerInterface::class)->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
471 471
 			} else {
472 472
 				// if location no longer exists, restore file in the root directory
473 473
 				if ($location !== '/'
474
-					&& (!$view->is_dir('files/' . $location)
475
-						|| !$view->isCreatable('files/' . $location))
474
+					&& (!$view->is_dir('files/'.$location)
475
+						|| !$view->isCreatable('files/'.$location))
476 476
 				) {
477 477
 					$location = '';
478 478
 				}
@@ -482,8 +482,8 @@  discard block
 block discarded – undo
482 482
 		// we need a  extension in case a file/dir with the same name already exists
483 483
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
484 484
 
485
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
486
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
485
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
486
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
487 487
 		if (!$view->file_exists($source)) {
488 488
 			return false;
489 489
 		}
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
 		}
496 496
 
497 497
 		$sourcePath = Filesystem::normalizePath($file);
498
-		$targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
498
+		$targetPath = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
499 499
 
500 500
 		$sourceNode = self::getNodeForPath($user, $sourcePath);
501 501
 		$targetNode = self::getNodeForPath($user, $targetPath, 'files');
@@ -513,8 +513,8 @@  discard block
 block discarded – undo
513 513
 		// handle the restore result
514 514
 		if ($restoreResult) {
515 515
 			$fakeRoot = $view->getRoot();
516
-			$view->chroot('/' . $user . '/files');
517
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
516
+			$view->chroot('/'.$user.'/files');
517
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
518 518
 			$view->chroot($fakeRoot);
519 519
 			Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]);
520 520
 
@@ -557,7 +557,7 @@  discard block
 block discarded – undo
557 557
 			$user = OC_User::getUser();
558 558
 			$rootView = new View('/');
559 559
 
560
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
560
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
561 561
 
562 562
 			[$owner, $ownerPath] = self::getUidAndFilename($target);
563 563
 
@@ -572,14 +572,14 @@  discard block
 block discarded – undo
572 572
 				$versionedFile = $file;
573 573
 			}
574 574
 
575
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
576
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
575
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
576
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
577 577
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
578 578
 				foreach ($versions as $v) {
579 579
 					if ($timestamp) {
580
-						$rootView->rename($user . '/files_trashbin/versions/' . static::getTrashFilename($versionedFile . '.v' . $v, $timestamp), $owner . '/files_versions/' . $ownerPath . '.v' . $v);
580
+						$rootView->rename($user.'/files_trashbin/versions/'.static::getTrashFilename($versionedFile.'.v'.$v, $timestamp), $owner.'/files_versions/'.$ownerPath.'.v'.$v);
581 581
 					} else {
582
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
582
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
583 583
 					}
584 584
 				}
585 585
 			}
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 	public static function deleteAll() {
593 593
 		$user = OC_User::getUser();
594 594
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
595
-		$view = new View('/' . $user);
595
+		$view = new View('/'.$user);
596 596
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
597 597
 
598 598
 		try {
@@ -667,7 +667,7 @@  discard block
 block discarded – undo
667 667
 	 */
668 668
 	public static function delete($filename, $user, $timestamp = null) {
669 669
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
670
-		$view = new View('/' . $user);
670
+		$view = new View('/'.$user);
671 671
 		$size = 0;
672 672
 
673 673
 		if ($timestamp) {
@@ -686,20 +686,20 @@  discard block
 block discarded – undo
686 686
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
687 687
 
688 688
 		try {
689
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
689
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
690 690
 		} catch (NotFoundException $e) {
691 691
 			return $size;
692 692
 		}
693 693
 
694 694
 		if ($node instanceof Folder) {
695
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
695
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
696 696
 		} elseif ($node instanceof File) {
697
-			$size += $view->filesize('/files_trashbin/files/' . $file);
697
+			$size += $view->filesize('/files_trashbin/files/'.$file);
698 698
 		}
699 699
 
700
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
700
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
701 701
 		$node->delete();
702
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
702
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
703 703
 
704 704
 		return $size;
705 705
 	}
@@ -709,20 +709,20 @@  discard block
 block discarded – undo
709 709
 	 * @param string $filename
710 710
 	 * @param ?int $timestamp
711 711
 	 */
712
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int|float {
712
+	private static function deleteVersions(View $view, $file, $filename, $timestamp, string $user): int | float {
713 713
 		$size = 0;
714 714
 		if (Server::get(IAppManager::class)->isEnabledForUser('files_versions')) {
715
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
716
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
717
-				$view->unlink('files_trashbin/versions/' . $file);
715
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
716
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
717
+				$view->unlink('files_trashbin/versions/'.$file);
718 718
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
719 719
 				foreach ($versions as $v) {
720 720
 					if ($timestamp) {
721
-						$size += $view->filesize('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
722
-						$view->unlink('/files_trashbin/versions/' . static::getTrashFilename($filename . '.v' . $v, $timestamp));
721
+						$size += $view->filesize('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
722
+						$view->unlink('/files_trashbin/versions/'.static::getTrashFilename($filename.'.v'.$v, $timestamp));
723 723
 					} else {
724
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
725
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
724
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
725
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
726 726
 					}
727 727
 				}
728 728
 			}
@@ -739,13 +739,13 @@  discard block
 block discarded – undo
739 739
 	 */
740 740
 	public static function file_exists($filename, $timestamp = null) {
741 741
 		$user = OC_User::getUser();
742
-		$view = new View('/' . $user);
742
+		$view = new View('/'.$user);
743 743
 
744 744
 		if ($timestamp) {
745 745
 			$filename = static::getTrashFilename($filename, $timestamp);
746 746
 		}
747 747
 
748
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
748
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
749 749
 		return $view->file_exists($target);
750 750
 	}
751 751
 
@@ -759,7 +759,7 @@  discard block
 block discarded – undo
759 759
 		$query = Server::get(IDBConnection::class)->getQueryBuilder();
760 760
 		$query->delete('files_trash')
761 761
 			->where($query->expr()->eq('user', $query->createNamedParameter($uid)));
762
-		return (bool)$query->executeStatement();
762
+		return (bool) $query->executeStatement();
763 763
 	}
764 764
 
765 765
 	/**
@@ -769,7 +769,7 @@  discard block
 block discarded – undo
769 769
 	 * @param string $user
770 770
 	 * @return int|float available free space for trash bin
771 771
 	 */
772
-	private static function calculateFreeSpace(int|float $trashbinSize, string $user): int|float {
772
+	private static function calculateFreeSpace(int | float $trashbinSize, string $user): int | float {
773 773
 		$configuredTrashbinSize = static::getConfiguredTrashbinSize($user);
774 774
 		if ($configuredTrashbinSize > -1) {
775 775
 			return $configuredTrashbinSize - $trashbinSize;
@@ -871,7 +871,7 @@  discard block
 block discarded – undo
871 871
 	 * @param int|float $availableSpace available disc space
872 872
 	 * @return int|float size of deleted files
873 873
 	 */
874
-	protected static function deleteFiles(array $files, string $user, int|float $availableSpace): int|float {
874
+	protected static function deleteFiles(array $files, string $user, int | float $availableSpace): int | float {
875 875
 		$expiration = Server::get(Expiration::class);
876 876
 		$size = 0;
877 877
 
@@ -880,7 +880,7 @@  discard block
 block discarded – undo
880 880
 				if ($availableSpace <= 0 && $expiration->isExpired($file['mtime'], true)) {
881 881
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
882 882
 					Server::get(LoggerInterface::class)->info(
883
-						'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
883
+						'remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota) for user "{user}"',
884 884
 						[
885 885
 							'app' => 'files_trashbin',
886 886
 							'user' => $user,
@@ -915,7 +915,7 @@  discard block
 block discarded – undo
915 915
 					$size += self::delete($filename, $user, $timestamp);
916 916
 					$count++;
917 917
 				} catch (NotPermittedException $e) {
918
-					Server::get(LoggerInterface::class)->warning('Removing "' . $filename . '" from trashbin failed for user "{user}"',
918
+					Server::get(LoggerInterface::class)->warning('Removing "'.$filename.'" from trashbin failed for user "{user}"',
919 919
 						[
920 920
 							'exception' => $e,
921 921
 							'app' => 'files_trashbin',
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
 					);
925 925
 				}
926 926
 				Server::get(LoggerInterface::class)->info(
927
-					'Remove "' . $filename . '" from trashbin for user "{user}" because it exceeds max retention obligation term.',
927
+					'Remove "'.$filename.'" from trashbin for user "{user}" because it exceeds max retention obligation term.',
928 928
 					[
929 929
 						'app' => 'files_trashbin',
930 930
 						'user' => $user,
@@ -947,22 +947,22 @@  discard block
 block discarded – undo
947 947
 	 * @return int|float
948 948
 	 * @throws Exceptions\CopyRecursiveException
949 949
 	 */
950
-	private static function copy_recursive($source, $destination, View $view): int|float {
950
+	private static function copy_recursive($source, $destination, View $view): int | float {
951 951
 		$size = 0;
952 952
 		if ($view->is_dir($source)) {
953 953
 			$view->mkdir($destination);
954 954
 			$view->touch($destination, $view->filemtime($source));
955 955
 			foreach ($view->getDirectoryContent($source) as $i) {
956
-				$pathDir = $source . '/' . $i['name'];
956
+				$pathDir = $source.'/'.$i['name'];
957 957
 				if ($view->is_dir($pathDir)) {
958
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
958
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
959 959
 				} else {
960 960
 					$size += $view->filesize($pathDir);
961
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
961
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
962 962
 					if (!$result) {
963 963
 						throw new CopyRecursiveException();
964 964
 					}
965
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
965
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
966 966
 				}
967 967
 			}
968 968
 		} else {
@@ -983,17 +983,17 @@  discard block
 block discarded – undo
983 983
 	 * @param int $timestamp timestamp when the file was deleted
984 984
 	 */
985 985
 	private static function getVersionsFromTrash($filename, $timestamp, string $user): array {
986
-		$view = new View('/' . $user . '/files_trashbin/versions');
986
+		$view = new View('/'.$user.'/files_trashbin/versions');
987 987
 		$versions = [];
988 988
 
989 989
 		/** @var \OC\Files\Storage\Storage $storage */
990
-		[$storage,] = $view->resolvePath('/');
990
+		[$storage, ] = $view->resolvePath('/');
991 991
 
992 992
 		$pattern = Server::get(IDBConnection::class)->escapeLikeParameter(basename($filename));
993 993
 		if ($timestamp) {
994 994
 			// fetch for old versions
995
-			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string)$timestamp);
996
-			$pattern .= '.v%.d' . $escapedTimestamp;
995
+			$escapedTimestamp = Server::get(IDBConnection::class)->escapeLikeParameter((string) $timestamp);
996
+			$pattern .= '.v%.d'.$escapedTimestamp;
997 997
 			$offset = -strlen($escapedTimestamp) - 2;
998 998
 		} else {
999 999
 			$pattern .= '.v%';
@@ -1005,7 +1005,7 @@  discard block
 block discarded – undo
1005 1005
 			Server::get(IDBConnection::class)->getQueryBuilder(),
1006 1006
 			Server::get(IFilesMetadataManager::class),
1007 1007
 		);
1008
-		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/' . $filename)), '/');
1008
+		$normalizedParentPath = ltrim(Filesystem::normalizePath(dirname('files_trashbin/versions/'.$filename)), '/');
1009 1009
 		$parentId = $cache->getId($normalizedParentPath);
1010 1010
 		if ($parentId === -1) {
1011 1011
 			return [];
@@ -1021,7 +1021,7 @@  discard block
 block discarded – undo
1021 1021
 		$result->closeCursor();
1022 1022
 
1023 1023
 		/** @var CacheEntry[] $matches */
1024
-		$matches = array_map(function (array $data) {
1024
+		$matches = array_map(function(array $data) {
1025 1025
 			return Cache::cacheEntryFromData($data, Server::get(IMimeTypeLoader::class));
1026 1026
 		}, $entries);
1027 1027
 
@@ -1051,18 +1051,18 @@  discard block
 block discarded – undo
1051 1051
 		$name = pathinfo($filename, PATHINFO_FILENAME);
1052 1052
 		$l = Util::getL10N('files_trashbin');
1053 1053
 
1054
-		$location = '/' . trim($location, '/');
1054
+		$location = '/'.trim($location, '/');
1055 1055
 
1056 1056
 		// if extension is not empty we set a dot in front of it
1057 1057
 		if ($ext !== '') {
1058
-			$ext = '.' . $ext;
1058
+			$ext = '.'.$ext;
1059 1059
 		}
1060 1060
 
1061
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
1061
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
1062 1062
 			$i = 2;
1063
-			$uniqueName = $name . ' (' . $l->t('restored') . ')' . $ext;
1064
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
1065
-				$uniqueName = $name . ' (' . $l->t('restored') . ' ' . $i . ')' . $ext;
1063
+			$uniqueName = $name.' ('.$l->t('restored').')'.$ext;
1064
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
1065
+				$uniqueName = $name.' ('.$l->t('restored').' '.$i.')'.$ext;
1066 1066
 				$i++;
1067 1067
 			}
1068 1068
 
@@ -1078,8 +1078,8 @@  discard block
 block discarded – undo
1078 1078
 	 * @param View $view file view on the root folder
1079 1079
 	 * @return int|float size of the folder
1080 1080
 	 */
1081
-	private static function calculateSize(View $view): int|float {
1082
-		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
1081
+	private static function calculateSize(View $view): int | float {
1082
+		$root = Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
1083 1083
 		if (!file_exists($root)) {
1084 1084
 			return 0;
1085 1085
 		}
@@ -1109,8 +1109,8 @@  discard block
 block discarded – undo
1109 1109
 	 * @param string $user user who owns the trash bin
1110 1110
 	 * @return int|float trash bin size
1111 1111
 	 */
1112
-	private static function getTrashbinSize(string $user): int|float {
1113
-		$view = new View('/' . $user);
1112
+	private static function getTrashbinSize(string $user): int | float {
1113
+		$view = new View('/'.$user);
1114 1114
 		$fileInfo = $view->getFileInfo('/files_trashbin');
1115 1115
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1116 1116
 	}
@@ -1122,7 +1122,7 @@  discard block
 block discarded – undo
1122 1122
 	 * @return bool
1123 1123
 	 */
1124 1124
 	public static function isEmpty($user) {
1125
-		$view = new View('/' . $user . '/files_trashbin');
1125
+		$view = new View('/'.$user.'/files_trashbin');
1126 1126
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1127 1127
 			while (($file = readdir($dh)) !== false) {
1128 1128
 				if (!Filesystem::isIgnoredDir($file)) {
@@ -1145,7 +1145,7 @@  discard block
 block discarded – undo
1145 1145
 	 * Return the filename used in the trash bin
1146 1146
 	 */
1147 1147
 	public static function getTrashFilename(string $filename, int $timestamp): string {
1148
-		$trashFilename = $filename . '.d' . $timestamp;
1148
+		$trashFilename = $filename.'.d'.$timestamp;
1149 1149
 		$length = strlen($trashFilename);
1150 1150
 		// oc_filecache `name` column has a limit of 250 chars
1151 1151
 		$maxLength = 250;
@@ -1173,7 +1173,7 @@  discard block
 block discarded – undo
1173 1173
 		}
1174 1174
 
1175 1175
 		$view = Server::get(View::class);
1176
-		$fullPath = '/' . $user . '/' . $baseDir . '/' . $path;
1176
+		$fullPath = '/'.$user.'/'.$baseDir.'/'.$path;
1177 1177
 
1178 1178
 		if (Filesystem::is_dir($path)) {
1179 1179
 			return new NonExistingFolder($rootFolder, $view, $fullPath);
Please login to merge, or discard this patch.