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