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