Completed
Pull Request — master (#32155)
by Thomas
26:27 queued 08:44
created

Trashbin::getUidAndFilename()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 12
nop 1
dl 0
loc 26
rs 8.8817
c 0
b 0
f 0
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
			$user = \OC::$server->getUserSession()->getUser();
117
			$uid= $user ? $user->getUID() : null;
118
		}
119
		if (!$uid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uid of type string|null is loosely compared to false; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

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