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