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