Trashbin   F
last analyzed

Complexity

Total Complexity 144

Size/Duplication

Total Lines 1043
Duplicated Lines 3.45 %

Coupling/Cohesion

Components 2
Dependencies 31

Importance

Changes 0
Metric Value
dl 36
loc 1043
rs 1.428
c 0
b 0
f 0
wmc 144
lcom 2
cbo 31

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A ensureFileScannedHook() 0 7 2
A getUidAndFilename() 0 25 5
A getLocations() 0 14 3
A getLocation() 0 10 2
B setUpTrash() 0 23 6
A copyFilesToUser() 0 19 2
A copyBackupForOwner() 0 18 2
A insertTrashEntry() 0 7 2
F move2trash() 0 94 17
C retainVersions() 12 39 14
A move() 0 13 2
A copy() 0 13 2
B restore() 0 55 9
B restoreVersions() 0 33 8
A deleteAll() 0 38 4
A emitTrashbinPreDelete() 0 3 1
A emitTrashbinPostDelete() 0 3 1
A delete() 0 25 3
B deleteVersions() 0 20 6
A file_exists() 0 11 2
A deleteUser() 0 4 1
A resizeTrash() 0 8 2
A expire() 0 14 1
A getQuota() 0 4 1
A scheduleExpire() 0 8 2
A deleteFiles() 0 25 5
A deleteExpiredFiles() 0 22 3
B copy_recursive() 0 28 6
B getVersionsFromTrash() 0 33 6
A getUniqueFilename() 0 25 4
A calculateSize() 0 24 4
A getTrashbinSize() 0 5 2
A registerListeners() 15 15 2
A registerHooks() 0 12 1
A resolvePrivateLink() 9 22 4
A isEmpty() 0 11 5
A preview_icon() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Trashbin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Trashbin, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Bastien Ho <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author Florin Peter <[email protected]>
7
 * @author Georg Ehrke <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Qingping Hou <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Sjors van der Pluijm <[email protected]>
16
 * @author Steven Bühner <[email protected]>
17
 * @author Thomas Müller <[email protected]>
18
 * @author Viktar Dubiniuk <[email protected]>
19
 * @author Vincent Petry <[email protected]>
20
 *
21
 * @copyright Copyright (c) 2018, ownCloud GmbH
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
38
namespace OCA\Files_Trashbin;
39
40
use OC\Files\Filesystem;
41
use OC\Files\View;
42
use OCA\Files_Trashbin\AppInfo\Application;
43
use OCA\Files_Trashbin\Command\Expire;
44
use OCP\Encryption\Keys\IStorage;
45
use OCP\Files\NotFoundException;
46
use OCP\User;
47
use Symfony\Component\EventDispatcher\GenericEvent;
48
use OCP\Files\Folder;
49
use OCP\Files\IRootFolder;
50
use OCP\IURLGenerator;
51
use Symfony\Component\EventDispatcher\EventDispatcher;
52
53
class Trashbin {
54
55
	/**
56
	 * @var IURLGenerator
57
	 */
58
	private $urlGenerator;
59
60
	/**
61
	 * @var IRootFolder
62
	 */
63
	private $rootFolder;
64
65
	/**
66
	 * @var EventDispatcher
67
	 */
68
	private $eventDispatcher;
69
70
	public function __construct(
71
		IRootFolder $rootFolder,
72
		IUrlGenerator $urlGenerator,
73
		EventDispatcher $eventDispatcher
74
	) {
75
		$this->rootFolder = $rootFolder;
76
		$this->urlGenerator = $urlGenerator;
77
		$this->eventDispatcher = $eventDispatcher;
78
	}
79
80
	/**
81
	 * Whether versions have already be rescanned during this PHP request
82
	 *
83
	 * @var bool
84
	 */
85
	private static $scannedVersions = false;
86
87
	/**
88
	 * Ensure we don't need to scan the file during the move to trash
89
	 * by triggering the scan in the pre-hook
90
	 *
91
	 * @param array $params
92
	 */
93
	public static function ensureFileScannedHook($params) {
94
		try {
95
			self::getUidAndFilename($params['path']);
96
		} catch (NotFoundException $e) {
97
			// nothing to scan for non existing files
98
		}
99
	}
100
101
	/**
102
	 * get the UID of the owner of the file and the path to the file relative to
103
	 * owners files folder
104
	 *
105
	 * @param string $filename
106
	 * @return array
107
	 * @throws \OC\User\NoUserException
108
	 */
109
	public static function getUidAndFilename($filename) {
110
		$uid = Filesystem::getOwner($filename);
111
		$userManager = \OC::$server->getUserManager();
112
		// if the user with the UID doesn't exists, e.g. because the UID points
113
		// to a remote user with a federated cloud ID we use the current logged-in
114
		// user. We need a valid local user to move the file to the right trash bin
115
		if (!$userManager->userExists($uid)) {
116
			$uid = User::getUser();
117
		}
118
		if (!$uid) {
119
			// no owner, usually because of share link from ext storage
120
			return [null, null];
121
		}
122
		Filesystem::initMountPoints($uid);
0 ignored issues
show
Bug introduced by
It seems like $uid defined by \OCP\User::getUser() on line 116 can also be of type boolean; however, OC\Files\Filesystem::initMountPoints() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
123
		if ($uid != User::getUser()) {
124
			$info = Filesystem::getFileInfo($filename);
125
			$ownerView = new View('/' . $uid . '/files');
126
			try {
127
				$filename = $ownerView->getPath($info['fileid']);
128
			} catch (NotFoundException $e) {
129
				$filename = null;
130
			}
131
		}
132
		return [$uid, $filename];
133
	}
134
135
	/**
136
	 * get original location of files for user
137
	 *
138
	 * @param string $user
139
	 * @return array (filename => array (timestamp => original location))
140
	 */
141
	public static function getLocations($user) {
142
		$query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
143
			. ' FROM `*PREFIX*files_trash` WHERE `user`=?');
144
		$result = $query->execute([$user]);
145
		$array = [];
146
		while ($row = $result->fetchRow()) {
147
			if (isset($array[$row['id']])) {
148
				$array[$row['id']][$row['timestamp']] = $row['location'];
149
			} else {
150
				$array[$row['id']] = [$row['timestamp'] => $row['location']];
151
			}
152
		}
153
		return $array;
154
	}
155
156
	/**
157
	 * get original location of file
158
	 *
159
	 * @param string $user
160
	 * @param string $filename
161
	 * @param string $timestamp
162
	 * @return string original location
163
	 */
164
	public static function getLocation($user, $filename, $timestamp) {
165
		$query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
166
			. ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
167
		$result = $query->execute([$user, $filename, $timestamp])->fetchAll();
168
		if (isset($result[0]['location'])) {
169
			return $result[0]['location'];
170
		} else {
171
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by OCA\Files_Trashbin\Trashbin::getLocation of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
172
		}
173
	}
174
175
	/**
176
	 * Sets up the trashbin for the given user
177
	 *
178
	 * @param string $user user id
179
	 * @return bool true if trashbin is setup and usable, false otherwise
180
	 */
181
	private static function setUpTrash($user) {
182
		$view = new View('/' . $user);
183
		if (!$view->is_dir('files_trashbin')) {
184
			$view->mkdir('files_trashbin');
185
		}
186
187
		if (!$view->isUpdatable('files_trashbin')) {
188
			// no trashbin access or denied
189
			return false;
190
		}
191
192
		if (!$view->is_dir('files_trashbin/files')) {
193
			$view->mkdir('files_trashbin/files');
194
		}
195
		if (!$view->is_dir('files_trashbin/versions')) {
196
			$view->mkdir('files_trashbin/versions');
197
		}
198
		if (!$view->is_dir('files_trashbin/keys')) {
199
			$view->mkdir('files_trashbin/keys');
200
		}
201
202
		return true;
203
	}
204
205
	/**
206
	 * copy file to owners trash
207
	 *
208
	 * @param string $sourcePath
209
	 * @param string $owner
210
	 * @param string $targetPath
211
	 * @param $user
212
	 * @param integer $timestamp
213
	 */
214
	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
215
		self::setUpTrash($owner);
216
217
		$targetFilename = \basename($targetPath);
218
		$targetLocation = \dirname($targetPath);
219
220
		$sourceFilename = \basename($sourcePath);
221
222
		$view = new View('/');
223
224
		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
225
		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
226
		self::copy_recursive($source, $target, $view);
227
228
		if ($view->file_exists($target)) {
229
			self::insertTrashEntry($user, $targetFilename, $targetLocation, $timestamp);
230
			self::scheduleExpire($user);
231
		}
232
	}
233
234
	/**
235
	 * Make a backup of a file into the trashbin for the owner
236
	 *
237
	 * @param string $ownerPath path relative to the owner's home folder and containing "files"
238
	 * @param string $owner user id of the owner
239
	 * @param int $timestamp deletion timestamp
240
	 */
241
	public static function copyBackupForOwner($ownerPath, $owner, $timestamp) {
242
		self::setUpTrash($owner);
243
244
		$targetFilename = \basename($ownerPath);
245
		$targetLocation = \dirname($ownerPath);
246
		$source = $owner . '/files/' . \ltrim($ownerPath, '/');
247
		$target = $owner . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
248
249
		$view = new View('/');
250
		self::copy_recursive($source, $target, $view);
251
252
		self::retainVersions($targetFilename, $owner, $ownerPath, $timestamp, null, true);
253
254
		if ($view->file_exists($target)) {
255
			self::insertTrashEntry($owner, $targetFilename, $targetLocation, $timestamp);
256
			self::scheduleExpire($owner);
257
		}
258
	}
259
260
	/**
261
	 *
262
	 */
263
	public static function insertTrashEntry($user, $targetFilename, $targetLocation, $timestamp) {
264
		$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
265
		$result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
266
		if (!$result) {
267
			\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OCP\Util::ERROR);
268
		}
269
	}
270
271
	/**
272
	 * move file to the trash bin
273
	 *
274
	 * @param string $file_path path to the deleted file/directory relative to the files root directory
275
	 * @return bool
276
	 */
277
	public static function move2trash($file_path) {
278
		// get the user for which the filesystem is setup
279
		$root = Filesystem::getRoot();
280
		list(, $user) = \explode('/', $root);
281
		list($owner, $ownerPath) = self::getUidAndFilename($file_path);
282
283
		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
284
		if ($owner === null) {
285
			$owner = $user;
286
			$ownerPath = $file_path;
287
		}
288
289
		$ownerView = new View('/' . $owner);
290
		// file has been deleted in between
291
		if ($ownerPath === null || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
292
			return true;
293
		}
294
295
		if (!self::setUpTrash($user)) {
296
			// trashbin not usable for user (ex: guest), switch to owner only
297
			$user = $owner;
298
			if (!self::setUpTrash($owner)) {
299
				// nothing to do as no trash is available anywheree
300
				return true;
301
			}
302
		}
303
		if ($owner !== $user) {
304
			// also setup for owner
305
			self::setUpTrash($owner);
306
		}
307
308
		$path_parts = \pathinfo($ownerPath);
309
310
		$filename = $path_parts['basename'];
311
		$location = $path_parts['dirname'];
312
		$timestamp = \time();
313
314
		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
315
316
		/** @var \OC\Files\Storage\Storage $trashStorage */
317
		list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
318
		/** @var \OC\Files\Storage\Storage $sourceStorage */
319
		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
320
		try {
321
			$moveSuccessful = true;
322
			if ($trashStorage->file_exists($trashInternalPath)) {
323
				$trashStorage->unlink($trashInternalPath);
324
			}
325
			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
326
		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
327
			$moveSuccessful = false;
328
			if ($trashStorage->file_exists($trashInternalPath)) {
329
				$trashStorage->unlink($trashInternalPath);
330
			}
331
			\OCP\Util::writeLog('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OCP\Util::ERROR);
332
		}
333
334
		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
335
			if ($sourceStorage->is_dir($sourceInternalPath)) {
336
				$sourceStorage->rmdir($sourceInternalPath);
337
			} else {
338
				$sourceStorage->unlink($sourceInternalPath);
339
			}
340
			return false;
341
		}
342
343
		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
344
345
		if ($moveSuccessful) {
346
			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
347
			$result = $query->execute([$filename, $timestamp, $location, $owner]);
348
			if (!$result) {
349
				\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated', \OCP\Util::ERROR);
350
			}
351
			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
352
				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
353
354
			self::retainVersions($filename, $owner, $ownerPath, $timestamp, $sourceStorage);
0 ignored issues
show
Documentation introduced by
$sourceStorage is of type object<OC\Files\Storage\Storage>, but the function expects a object<OCP\Encryption\Keys\IStorage>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
355
356
			// if owner !== user we need to also add a copy to the owners trash
357
			if ($user !== $owner) {
358
				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
359
			}
360
		}
361
362
		self::scheduleExpire($user);
363
364
		// if owner !== user we also need to update the owners trash size
365
		if ($owner !== $user) {
366
			self::scheduleExpire($owner);
367
		}
368
369
		return $moveSuccessful;
370
	}
371
372
	/**
373
	 * Move file versions to trash so that they can be restored later
374
	 *
375
	 * @param string $filename of deleted file
376
	 * @param string $owner owner user id
377
	 * @param string $ownerPath path relative to the owner's home storage
378
	 * @param integer $timestamp when the file was deleted
379
	 * @param IStorage|null $sourceStorage
380
	 * @param bool $forceCopy true to only make a copy of the versions into the trashbin
381
	 * @throws Exceptions\CopyRecursiveException
382
	 */
383
	private static function retainVersions($filename, $owner, $ownerPath, $timestamp, $sourceStorage = null, $forceCopy = false) {
384
		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
385
			$copyKeysResult = false;
386
387
			/**
388
			 * In case if encryption is enabled then we need to retain the keys which were
389
			 * deleted due to move operation to trashbin.
390
			 */
391
			if ($sourceStorage !== null) {
392
				$copyKeysResult = $sourceStorage->retainKeys($filename, $owner, $ownerPath, $timestamp, $sourceStorage);
393
			}
394
395
			$user = User::getUser();
396
			$rootView = new View('/');
397
398
			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
399 View Code Duplication
				if ($owner !== $user || $forceCopy) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . \basename($ownerPath) . '.d' . $timestamp, $rootView);
401
				}
402 View Code Duplication
				if (!$forceCopy) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
					self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
404
				}
405
			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
406
				foreach ($versions as $v) {
407 View Code Duplication
					if ($owner !== $user || $forceCopy) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
409
					}
410 View Code Duplication
					if (!$forceCopy) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
411
						self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
412
					}
413
				}
414
			}
415
416
			if ($copyKeysResult === true) {
417
				$filePath = $rootView->getAbsolutePath('/files/' . $ownerPath);
418
				$sourceStorage->deleteAllFileKeys($filePath);
0 ignored issues
show
Bug introduced by
It seems like $sourceStorage is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
419
			}
420
		}
421
	}
422
423
	/**
424
	 * Move a file or folder on storage level
425
	 *
426
	 * @param View $view
427
	 * @param string $source
428
	 * @param string $target
429
	 * @return bool
430
	 */
431
	private static function move(View $view, $source, $target) {
432
		/** @var \OC\Files\Storage\Storage $sourceStorage */
433
		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
434
		/** @var \OC\Files\Storage\Storage $targetStorage */
435
		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
436
		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
437
438
		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
439
		if ($result) {
440
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
441
		}
442
		return $result;
443
	}
444
445
	/**
446
	 * Copy a file or folder on storage level
447
	 *
448
	 * @param View $view
449
	 * @param string $source
450
	 * @param string $target
451
	 * @return bool
452
	 */
453
	private static function copy(View $view, $source, $target) {
454
		/** @var \OC\Files\Storage\Storage $sourceStorage */
455
		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
456
		/** @var \OC\Files\Storage\Storage $targetStorage */
457
		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
458
		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
459
460
		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
461
		if ($result) {
462
			$targetStorage->getUpdater()->update($targetInternalPath);
463
		}
464
		return $result;
465
	}
466
467
	/**
468
	 * Restore a file or folder from trash bin
469
	 *
470
	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
471
	 * including the timestamp suffix ".d12345678"
472
	 * @param string $filename name of the file/folder
473
	 * @param int $timestamp time when the file/folder was deleted
474
	 *
475
	 * @return bool true on success, false otherwise
476
	 */
477
	public static function restore($file, $filename, $timestamp) {
478
		$user = User::getUser();
479
		$view = new View('/' . $user);
480
481
		$location = '';
482
		if ($timestamp) {
483
			$location = self::getLocation($user, $filename, $timestamp);
0 ignored issues
show
Bug introduced by
It seems like $user defined by \OCP\User::getUser() on line 478 can also be of type boolean; however, OCA\Files_Trashbin\Trashbin::getLocation() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
484
			if ($location === false) {
485
				\OCP\Util::writeLog('files_trashbin', 'Original location of file ' . $filename .
486
					' not found in database, hence restoring into user\'s root instead', \OCP\Util::DEBUG);
487
			} else {
488
				// if location no longer exists, restore file in the root directory
489
				if ($location !== '/' &&
490
					(!$view->is_dir('files/' . $location) ||
491
						!$view->isCreatable('files/' . $location))
492
				) {
493
					$location = '';
494
				}
495
			}
496
		}
497
498
		// we need a  extension in case a file/dir with the same name already exists
499
		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
500
501
		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
502
		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
503
		if (!$view->file_exists($source)) {
504
			return false;
505
		}
506
		$mtime = $view->filemtime($source);
507
508
		// restore file
509
		$restoreResult = $view->rename($source, $target);
510
511
		// handle the restore result
512
		if ($restoreResult) {
513
			$fakeRoot = $view->getRoot();
514
			$view->chroot('/' . $user . '/files');
515
			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
516
			$view->chroot($fakeRoot);
517
			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
518
				'trashPath' => Filesystem::normalizePath($file)]);
519
520
			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
521
522
			if ($timestamp) {
523
				$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
524
				$query->execute([$user, $filename, $timestamp]);
525
			}
526
527
			return true;
528
		}
529
530
		return false;
531
	}
532
533
	/**
534
	 * restore versions from trash bin
535
	 *
536
	 * @param View $view file view
537
	 * @param string $file complete path to file
538
	 * @param string $filename name of file once it was deleted
539
	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
540
	 * @param string $location location if file
541
	 * @param int $timestamp deletion time
542
	 * @return false|null
543
	 */
544
	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
545
		if (\OCP\App::isEnabled('files_versions')) {
546
			$user = User::getUser();
547
			$rootView = new View('/');
548
549
			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
550
551
			list($owner, $ownerPath) = self::getUidAndFilename($target);
552
553
			// file has been deleted in between
554
			if (empty($ownerPath)) {
555
				return false;
556
			}
557
558
			if ($timestamp) {
559
				$versionedFile = $filename;
560
			} else {
561
				$versionedFile = $file;
562
			}
563
564
			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
565
				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
566
			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
567
				foreach ($versions as $v) {
568
					if ($timestamp) {
569
						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
570
					} else {
571
						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
572
					}
573
				}
574
			}
575
		}
576
	}
577
578
	/**
579
	 * delete all files from the trash
580
	 */
581
	public static function deleteAll() {
582
		$user = User::getUser();
583
		$view = new View('/' . $user);
584
		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
585
586
		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
587
		$filePaths = [];
588
		foreach ($fileInfos as $fileInfo) {
589
			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
590
		}
591
		unset($fileInfos); // save memory
592
593
		// Bulk PreDelete-Hook
594
		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
595
596
		// Single-File Hooks
597
		foreach ($filePaths as $path) {
598
			self::emitTrashbinPreDelete($path);
599
		}
600
601
		// actual file deletion
602
		$view->deleteAll('files_trashbin');
603
		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
604
		$query->execute([$user]);
605
606
		// Bulk PostDelete-Hook
607
		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
608
609
		// Single-File Hooks
610
		foreach ($filePaths as $path) {
611
			self::emitTrashbinPostDelete($path);
612
		}
613
614
		$view->mkdir('files_trashbin');
615
		$view->mkdir('files_trashbin/files');
616
617
		return true;
618
	}
619
620
	/**
621
	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
622
	 * @param string $path
623
	 */
624
	protected static function emitTrashbinPreDelete($path) {
625
		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
626
	}
627
628
	/**
629
	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
630
	 * @param string $path
631
	 */
632
	protected static function emitTrashbinPostDelete($path) {
633
		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
634
	}
635
636
	/**
637
	 * delete file from trash bin permanently
638
	 *
639
	 * @param string $filename path to the file
640
	 * @param string $user
641
	 * @param int $timestamp of deletion time
642
	 *
643
	 * @return int size of deleted files
644
	 */
645
	public static function delete($filename, $user, $timestamp = null) {
646
		$view = new View('/' . $user);
647
		$size = 0;
648
649
		if ($timestamp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timestamp of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
650
			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
651
			$query->execute([$user, $filename, $timestamp]);
652
			$file = $filename . '.d' . $timestamp;
653
		} else {
654
			$file = $filename;
655
		}
656
657
		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
658
659
		if ($view->is_dir('/files_trashbin/files/' . $file)) {
660
			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
661
		} else {
662
			$size += $view->filesize('/files_trashbin/files/' . $file);
663
		}
664
		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
665
		$view->unlink('/files_trashbin/files/' . $file);
666
		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
667
668
		return $size;
669
	}
670
671
	/**
672
	 * @param View $view
673
	 * @param string $file
674
	 * @param string $filename
675
	 * @param integer|null $timestamp
676
	 * @param string $user
677
	 * @return int
678
	 */
679
	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
680
		$size = 0;
681
		if (\OCP\App::isEnabled('files_versions')) {
682
			if ($view->is_dir('files_trashbin/versions/' . $file)) {
683
				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
684
				$view->unlink('files_trashbin/versions/' . $file);
685
			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
686
				foreach ($versions as $v) {
687
					if ($timestamp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timestamp of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
688
						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
689
						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
690
					} else {
691
						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
692
						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
693
					}
694
				}
695
			}
696
		}
697
		return $size;
698
	}
699
700
	/**
701
	 * check to see whether a file exists in trashbin
702
	 *
703
	 * @param string $filename path to the file
704
	 * @param int $timestamp of deletion time
705
	 * @return bool true if file exists, otherwise false
706
	 */
707
	public static function file_exists($filename, $timestamp = null) {
708
		$user = User::getUser();
709
		$view = new View('/' . $user);
710
711
		if ($timestamp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timestamp of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
712
			$filename = $filename . '.d' . $timestamp;
713
		}
714
715
		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
716
		return $view->file_exists($target);
717
	}
718
719
	/**
720
	 * deletes used space for trash bin in db if user was deleted
721
	 *
722
	 * @param string $uid id of deleted user
723
	 * @return bool result of db delete operation
724
	 */
725
	public static function deleteUser($uid) {
726
		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
727
		return $query->execute([$uid]);
728
	}
729
730
	/**
731
	 * resize trash bin if necessary after a new file was added to ownCloud
732
	 *
733
	 * @param string $user user id
734
	 */
735
	public static function resizeTrash($user) {
736
		$size = self::getTrashbinSize($user);
737
		$freeSpace = self::getQuota()->calculateFreeSpace($size, $user);
738
739
		if ($freeSpace < 0) {
740
			self::scheduleExpire($user);
741
		}
742
	}
743
744
	/**
745
	 * clean up the trash bin
746
	 *
747
	 * @param string $user
748
	 */
749
	public static function expire($user) {
750
		$trashBinSize = self::getTrashbinSize($user);
751
		$availableSpace = self::getQuota()->calculateFreeSpace($trashBinSize, $user);
752
753
		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
754
755
		// delete all files older then $retention_obligation
756
		list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user);
757
758
		$availableSpace += $delSize;
759
760
		// delete files from trash until we meet the trash bin size limit again
761
		self::deleteFiles(\array_slice($dirContent, $count), $user, $availableSpace);
762
	}
763
764
	/**
765
	 * @return Quota
766
	 */
767
	protected static function getQuota() {
768
		$application = new Application();
769
		return $application->getContainer()->query('Quota');
770
	}
771
772
	/**
773
	 * @param string $user
774
	 */
775
	private static function scheduleExpire($user) {
776
		// let the admin disable auto expire
777
		$application = new Application();
778
		$expiration = $application->getContainer()->query('Expiration');
779
		if ($expiration->isEnabled()) {
780
			\OC::$server->getCommandBus()->push(new Expire($user));
781
		}
782
	}
783
784
	/**
785
	 * if the size limit for the trash bin is reached, we delete the oldest
786
	 * files in the trash bin until we meet the limit again
787
	 *
788
	 * @param array $files
789
	 * @param string $user
790
	 * @param int $availableSpace available disc space
791
	 * @return int size of deleted files
792
	 */
793
	protected static function deleteFiles($files, $user, $availableSpace) {
794
		$application = new Application();
795
		$expiration = $application->getContainer()->query('Expiration');
796
		$size = 0;
797
798
		if ($availableSpace < 0) {
799
			foreach ($files as $file) {
800
				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
801
					$tmp = self::delete($file['name'], $user, $file['mtime']);
802
					$message = \sprintf(
803
						'remove "%s" (%dB) to meet the limit of trash bin size (%d%% of available quota)',
804
						$file['name'],
805
						$tmp,
806
						self::getQuota()->getPurgeLimit()
807
					);
808
					\OCP\Util::writeLog('files_trashbin', $message, \OCP\Util::INFO);
809
					$availableSpace += $tmp;
810
					$size += $tmp;
811
				} else {
812
					break;
813
				}
814
			}
815
		}
816
		return $size;
817
	}
818
819
	/**
820
	 * delete files older then max storage time
821
	 *
822
	 * @param array $files list of files sorted by mtime
823
	 * @param string $user
824
	 * @return integer[] size of deleted files and number of deleted files
825
	 */
826
	public static function deleteExpiredFiles($files, $user) {
827
		$application = new Application();
828
		$expiration = $application->getContainer()->query('Expiration');
829
		$size = 0;
830
		$count = 0;
831
		foreach ($files as $file) {
832
			$timestamp = $file['mtime'];
833
			$filename = $file['name'];
834
			if ($expiration->isExpired($timestamp)) {
835
				$count++;
836
				$size += self::delete($filename, $user, $timestamp);
837
				\OC::$server->getLogger()->info(
838
					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
839
					['app' => 'files_trashbin']
840
				);
841
			} else {
842
				break;
843
			}
844
		}
845
846
		return [$size, $count];
847
	}
848
849
	/**
850
	 * recursive copy to copy a whole directory
851
	 *
852
	 * @param string $source source path, relative to the users files directory
853
	 * @param string $destination destination path relative to the users root directoy
854
	 * @param View $view file view for the users root directory
855
	 * @return int
856
	 * @throws Exceptions\CopyRecursiveException
857
	 */
858
	private static function copy_recursive($source, $destination, View $view) {
859
		$size = 0;
860
		if ($view->is_dir($source)) {
861
			$view->mkdir($destination);
862
			$view->touch($destination, $view->filemtime($source));
863
			foreach ($view->getDirectoryContent($source) as $i) {
864
				$pathDir = $source . '/' . $i['name'];
865
				if ($view->is_dir($pathDir)) {
866
					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
867
				} else {
868
					$size += $view->filesize($pathDir);
869
					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
870
					if (!$result) {
871
						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
872
					}
873
					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
874
				}
875
			}
876
		} else {
877
			$size += $view->filesize($source);
878
			$result = $view->copy($source, $destination);
879
			if (!$result) {
880
				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
881
			}
882
			$view->touch($destination, $view->filemtime($source));
883
		}
884
		return $size;
885
	}
886
887
	/**
888
	 * find all versions which belong to the file we want to restore
889
	 *
890
	 * @param string $filename name of the file which should be restored
891
	 * @param int $timestamp timestamp when the file was deleted
892
	 * @return array
893
	 */
894
	private static function getVersionsFromTrash($filename, $timestamp, $user) {
895
		$view = new View('/' . $user . '/files_trashbin/versions');
896
		$versions = [];
897
898
		//force rescan of versions, local storage may not have updated the cache
899
		if (!self::$scannedVersions) {
900
			/** @var \OC\Files\Storage\Storage $storage */
901
			list($storage, ) = $view->resolvePath('/');
902
			$storage->getScanner()->scan('files_trashbin/versions');
903
			self::$scannedVersions = true;
904
		}
905
906
		if ($timestamp) {
907
			// fetch for old versions
908
			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
909
			$offset = -\strlen($timestamp) - 2;
910
		} else {
911
			$matches = $view->searchRaw($filename . '.v%');
912
		}
913
914
		if (\is_array($matches)) {
915
			foreach ($matches as $ma) {
916
				if ($timestamp) {
917
					$parts = \explode('.v', \substr($ma['path'], 0, $offset));
0 ignored issues
show
Bug introduced by
The variable $offset does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
918
					$versions[] = (\end($parts));
919
				} else {
920
					$parts = \explode('.v', $ma);
921
					$versions[] = (\end($parts));
922
				}
923
			}
924
		}
925
		return $versions;
926
	}
927
928
	/**
929
	 * find unique extension for restored file if a file with the same name already exists
930
	 *
931
	 * @param string $location where the file should be restored
932
	 * @param string $filename name of the file
933
	 * @param View $view filesystem view relative to users root directory
934
	 * @return string with unique extension
935
	 */
936
	private static function getUniqueFilename($location, $filename, View $view) {
937
		$ext = \pathinfo($filename, PATHINFO_EXTENSION);
938
		$name = \pathinfo($filename, PATHINFO_FILENAME);
939
		$l = \OC::$server->getL10N('files_trashbin');
940
941
		$location = '/' . \trim($location, '/');
942
943
		// if extension is not empty we set a dot in front of it
944
		if ($ext !== '') {
945
			$ext = '.' . $ext;
946
		}
947
948
		if ($view->file_exists('files' . $location . '/' . $filename)) {
949
			$i = 2;
950
			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
951
			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
952
				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
953
				$i++;
954
			}
955
956
			return $uniqueName;
957
		}
958
959
		return $filename;
960
	}
961
962
	/**
963
	 * get the size from a given root folder
964
	 *
965
	 * @param View $view file view on the root folder
966
	 * @return integer size of the folder
967
	 */
968
	private static function calculateSize($view) {
969
		$root = \OC::$server->getConfig()->getSystemValue('datadirectory') . $view->getAbsolutePath('');
970
		if (!\file_exists($root)) {
971
			return 0;
972
		}
973
		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
974
		$size = 0;
975
976
		/**
977
		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
978
		 * This bug is fixed in PHP 5.5.9 or before
979
		 * See #8376
980
		 */
981
		$iterator->rewind();
982
		while ($iterator->valid()) {
983
			$path = $iterator->current();
984
			$relpath = \substr($path, \strlen($root) - 1);
985
			if (!$view->is_dir($relpath)) {
986
				$size += $view->filesize($relpath);
987
			}
988
			$iterator->next();
989
		}
990
		return $size;
991
	}
992
993
	/**
994
	 * get current size of trash bin from a given user
995
	 *
996
	 * @param string $user user who owns the trash bin
997
	 * @return integer trash bin size
998
	 */
999
	private static function getTrashbinSize($user) {
1000
		$view = new View('/' . $user);
1001
		$fileInfo = $view->getFileInfo('/files_trashbin');
1002
		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
1003
	}
1004
1005
	/**
1006
	 * Register listeners
1007
	 */
1008 View Code Duplication
	public function registerListeners() {
1009
		$this->eventDispatcher->addListener(
1010
			'files.resolvePrivateLink',
1011
			function (GenericEvent $event) {
1012
				$uid = $event->getArgument('uid');
1013
				$fileId = $event->getArgument('fileid');
1014
1015
				$link = $this->resolvePrivateLink($uid, $fileId);
1016
1017
				if ($link !== null) {
1018
					$event->setArgument('resolvedWebLink', $link);
1019
				}
1020
			}
1021
		);
1022
	}
1023
1024
	/**
1025
	 * register hooks
1026
	 */
1027
	public static function registerHooks() {
1028
		// create storage wrapper on setup
1029
		\OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
1030
		//Listen to delete user signal
1031
		\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
1032
		//Listen to post write hook
1033
		\OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
1034
		// pre and post-rename, disable trash logic for the copy+unlink case
1035
		\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
1036
		\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
1037
		\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
1038
	}
1039
1040
	/**
1041
	 * Resolves web URL that points to the trashbin view of the given file
1042
	 *
1043
	 * @param string $uid user id
1044
	 * @param string $fileId file id
1045
	 * @return string|null view URL or null if the file is not found or not accessible
1046
	 */
1047
	public function resolvePrivateLink($uid, $fileId) {
1048
		if ($this->rootFolder->nodeExists($uid . '/files_trashbin/files/')) {
1049
			$baseFolder = $this->rootFolder->get($uid . '/files_trashbin/files/');
1050
			$files = $baseFolder->getById($fileId);
1051
			if (!empty($files)) {
1052
				$params['view'] = 'trashbin';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1053
				$file = \current($files);
1054 View Code Duplication
				if ($file instanceof Folder) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1055
					// set the full path to enter the folder
1056
					$params['dir'] = $baseFolder->getRelativePath($file->getPath());
1057
				} else {
1058
					// set parent path as dir
1059
					$params['dir'] = $baseFolder->getRelativePath($file->getParent()->getPath());
1060
					// and scroll to the entry
1061
					$params['scrollto'] = $file->getName();
1062
				}
1063
				return $this->urlGenerator->linkToRoute('files.view.index', $params);
1064
			}
1065
		}
1066
1067
		return null;
1068
	}
1069
1070
	/**
1071
	 * check if trash bin is empty for a given user
1072
	 *
1073
	 * @param string $user
1074
	 * @return bool
1075
	 */
1076
	public static function isEmpty($user) {
1077
		$view = new View('/' . $user . '/files_trashbin');
1078
		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
1079
			while ($file = \readdir($dh)) {
1080
				if (!Filesystem::isIgnoredDir($file)) {
1081
					return false;
1082
				}
1083
			}
1084
		}
1085
		return true;
1086
	}
1087
1088
	/**
1089
	 * @param $path
1090
	 * @return string
1091
	 */
1092
	public static function preview_icon($path) {
1093
		return \OCP\Util::linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
1094
	}
1095
}
1096