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