Completed
Push — stable8 ( 42720e...c45eda )
by
unknown
35s
created

Trashbin   F

Complexity

Total Complexity 133

Size/Duplication

Total Lines 952
Duplicated Lines 2 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 133
c 1
b 0
f 0
lcom 1
cbo 22
dl 19
loc 952
rs 1.263

30 Methods

Rating   Name   Duplication   Size   Complexity  
A getUidAndFilename() 10 10 2
A getLocations() 0 14 3
A getLocation() 0 10 2
B setUpTrash() 0 15 5
A copyFilesToOwner() 0 23 3
F move2trash() 0 82 14
C retainVersions() 3 33 8
B retainEncryptionKeys() 3 35 6
C restore() 0 63 8
C restoreVersions() 0 42 8
C restoreEncryptionKeys() 0 42 8
A deleteAll() 0 9 1
B delete() 0 26 3
B deleteVersions() 0 21 6
A deleteEncryptionKeys() 0 18 4
A file_exists() 0 13 2
A deleteUser() 0 4 1
C calculateFreeSpace() 3 35 8
A resizeTrash() 0 10 2
B expire() 0 28 2
A deleteFiles() 0 17 4
A deleteExpiredFiles() 0 17 3
B copy_recursive() 0 28 6
B getVersionsFromTrash() 0 33 6
B getUniqueFilename() 0 23 4
B calculateSize() 0 24 4
A getTrashbinSize() 0 5 2
A registerHooks() 0 11 1
B isEmpty() 0 12 6
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
 * ownCloud - trash bin
4
 *
5
 * @author Bjoern Schiessle
6
 * @copyright 2013 Bjoern Schiessle [email protected]
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
10
 * License as published by the Free Software Foundation; either
11
 * version 3 of the License, or any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public
19
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
23
namespace OCA\Files_Trashbin;
24
25
use OC\Files\Filesystem;
26
27
class Trashbin {
28
	// how long do we keep files in the trash bin if no other value is defined in the config file (unit: days)
29
30
	const DEFAULT_RETENTION_OBLIGATION = 30;
31
32
	// unit: percentage; 50% of available disk space/quota
33
	const DEFAULTMAXSIZE = 50;
34
35
	/**
36
	 * Whether versions have already be rescanned during this PHP request
37
	 *
38
	 * @var bool
39
	 */
40
	private static $scannedVersions = false;
41
42 View Code Duplication
	public static function getUidAndFilename($filename) {
43
		$uid = \OC\Files\Filesystem::getOwner($filename);
44
		\OC\Files\Filesystem::initMountPoints($uid);
0 ignored issues
show
Bug introduced by
It seems like $uid defined by \OC\Files\Filesystem::getOwner($filename) on line 43 can also be of type false or null; 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...
45
		if ($uid != \OCP\User::getUser()) {
46
			$info = \OC\Files\Filesystem::getFileInfo($filename);
47
			$ownerView = new \OC\Files\View('/' . $uid . '/files');
48
			$filename = $ownerView->getPath($info['fileid']);
49
		}
50
		return array($uid, $filename);
51
	}
52
53
	/**
54
	 * get original location of files for user
55
	 *
56
	 * @param string $user
57
	 * @return array (filename => array (timestamp => original location))
58
	 */
59
	public static function getLocations($user) {
60
		$query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
61
			. ' FROM `*PREFIX*files_trash` WHERE `user`=?');
62
		$result = $query->execute(array($user));
63
		$array = array();
64
		while ($row = $result->fetchRow()) {
65
			if (isset($array[$row['id']])) {
66
				$array[$row['id']][$row['timestamp']] = $row['location'];
67
			} else {
68
				$array[$row['id']] = array($row['timestamp'] => $row['location']);
69
			}
70
		}
71
		return $array;
72
	}
73
74
	/**
75
	 * get original location of file
76
	 *
77
	 * @param string $user
78
	 * @param string $filename
79
	 * @param string $timestamp
80
	 * @return string original location
81
	 */
82
	public static function getLocation($user, $filename, $timestamp) {
83
		$query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
84
			. ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
85
		$result = $query->execute(array($user, $filename, $timestamp))->fetchAll();
86
		if (isset($result[0]['location'])) {
87
			return $result[0]['location'];
88
		} else {
89
			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...
90
		}
91
	}
92
93
	private static function setUpTrash($user) {
94
		$view = new \OC\Files\View('/' . $user);
95
		if (!$view->is_dir('files_trashbin')) {
96
			$view->mkdir('files_trashbin');
97
		}
98
		if (!$view->is_dir('files_trashbin/files')) {
99
			$view->mkdir('files_trashbin/files');
100
		}
101
		if (!$view->is_dir('files_trashbin/versions')) {
102
			$view->mkdir('files_trashbin/versions');
103
		}
104
		if (!$view->is_dir('files_trashbin/keys')) {
105
			$view->mkdir('files_trashbin/keys');
106
		}
107
	}
108
109
110
	/**
111
	 * copy file to owners trash
112
	 * @param string $sourcePath
113
	 * @param string $owner
114
	 * @param string $ownerPath
115
	 * @param integer $timestamp
116
	 */
117
	private static function copyFilesToOwner($sourcePath, $owner, $ownerPath, $timestamp) {
118
		self::setUpTrash($owner);
119
120
		$ownerFilename = basename($ownerPath);
121
		$ownerLocation = dirname($ownerPath);
122
123
		$sourceFilename = basename($sourcePath);
124
125
		$view = new \OC\Files\View('/');
126
127
		$source = \OCP\User::getUser() . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
128
		$target = $owner . '/files_trashbin/files/' . $ownerFilename . '.d' . $timestamp;
129
		self::copy_recursive($source, $target, $view);
130
131
132
		if ($view->file_exists($target)) {
133
			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
134
			$result = $query->execute(array($ownerFilename, $timestamp, $ownerLocation, $owner));
135
			if (!$result) {
136
				\OC_Log::write('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OC_log::ERROR);
137
			}
138
		}
139
	}
140
141
142
	/**
143
	 * move file to the trash bin
144
	 *
145
	 * @param string $file_path path to the deleted file/directory relative to the files root directory
146
	 */
147
	public static function move2trash($file_path) {
148
		// get the user for which the filesystem is setup
149
		$root = Filesystem::getRoot();
150
		list(, $user) = explode('/', $root);
151
		$size = 0;
152
		list($owner, $ownerPath) = self::getUidAndFilename($file_path);
153
154
		$view = new \OC\Files\View('/' . $user);
155
		// file has been deleted in between
156
		if (is_null($ownerPath) || $ownerPath === '' || !$view->file_exists('/files/' . $file_path)) {
157
			return true;
158
		}
159
160
		self::setUpTrash($user);
161
		if ($owner !== $user) {
162
			// also setup for owner
163
			self::setUpTrash($owner);
164
		}
165
166
		$path_parts = pathinfo($file_path);
167
168
		$filename = $path_parts['basename'];
169
		$location = $path_parts['dirname'];
170
		$timestamp = time();
171
172
		$userTrashSize = self::getTrashbinSize($user);
173
174
		// disable proxy to prevent recursive calls
175
		$proxyStatus = \OC_FileProxy::$enabled;
176
		\OC_FileProxy::$enabled = false;
177
		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
178
		try {
179
			$sizeOfAddedFiles = $view->filesize('/files/' . $file_path);
180
			if ($view->file_exists($trashPath)) {
181
				$view->unlink($trashPath);
182
			}
183
			$view->rename('/files/' . $file_path, $trashPath);
184
		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
185
			$sizeOfAddedFiles = false;
186
			if ($view->file_exists($trashPath)) {
187
				$view->deleteAll($trashPath);
188
			}
189
			\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
190
		}
191
		\OC_FileProxy::$enabled = $proxyStatus;
192
193
		if ($view->file_exists('/files/' . $file_path)) { // failed to delete the original file, abort
194
			$view->unlink($trashPath);
195
			return false;
196
		}
197
198
		if ($sizeOfAddedFiles !== false) {
199
			$size = $sizeOfAddedFiles;
200
			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
201
			$result = $query->execute(array($filename, $timestamp, $location, $user));
202
			if (!$result) {
203
				\OC_Log::write('files_trashbin', 'trash bin database couldn\'t be updated', \OC_log::ERROR);
204
			}
205
			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => \OC\Files\Filesystem::normalizePath($file_path),
206
				'trashPath' => \OC\Files\Filesystem::normalizePath($filename . '.d' . $timestamp)));
207
208
			$size += self::retainVersions($file_path, $filename, $owner, $ownerPath, $timestamp);
209
			$size += self::retainEncryptionKeys($file_path, $filename, $timestamp);
210
211
			// if owner !== user we need to also add a copy to the owners trash
212
			if ($user !== $owner) {
213
				self::copyFilesToOwner($file_path, $owner, $ownerPath, $timestamp);
214
			}
215
		}
216
217
		$userTrashSize += $size;
218
		$userTrashSize -= self::expire($userTrashSize, $user);
219
220
		// if owner !== user we also need to update the owners trash size
221
		if ($owner !== $user) {
222
			$ownerTrashSize = self::getTrashbinSize($owner);
223
			$ownerTrashSize += $size;
224
			$ownerTrashSize -= self::expire($ownerTrashSize, $owner);
225
		}
226
227
		return ($sizeOfAddedFiles === false) ? false : true;
228
	}
229
230
	/**
231
	 * Move file versions to trash so that they can be restored later
232
	 *
233
	 * @param string $file_path path to original file
234
	 * @param string $filename of deleted file
235
	 * @param string $owner owner user id
236
	 * @param string $ownerPath path relative to the owner's home storage
237
	 * @param integer $timestamp when the file was deleted
238
	 *
239
	 * @return int size of stored versions
240
	 */
241
	private static function retainVersions($file_path, $filename, $owner, $ownerPath, $timestamp) {
0 ignored issues
show
Unused Code introduced by
The parameter $file_path is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
		$size = 0;
243
		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
244
245
			// disable proxy to prevent recursive calls
246
			$proxyStatus = \OC_FileProxy::$enabled;
247
			\OC_FileProxy::$enabled = false;
248
249
			$user = \OCP\User::getUser();
250
			$rootView = new \OC\Files\View('/');
251
252
			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
253
				$size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath));
254 View Code Duplication
				if ($owner !== $user) {
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...
255
					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
256
				}
257
				$rootView->rename($owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
258
			} else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
259
				foreach ($versions as $v) {
260
					$size += $rootView->filesize($owner . '/files_versions' . $v['path'] . '.v' . $v['version']);
261
					if ($owner !== $user) {
262
						$rootView->copy($owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
263
					}
264
					$rootView->rename($owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
265
				}
266
			}
267
268
			// enable proxy
269
			\OC_FileProxy::$enabled = $proxyStatus;
270
		}
271
272
		return $size;
273
	}
274
275
	/**
276
	 * Move encryption keys to trash so that they can be restored later
277
	 *
278
	 * @param string $file_path path to original file
279
	 * @param string $filename of deleted file
280
	 * @param integer $timestamp when the file was deleted
281
	 *
282
	 * @return int size of encryption keys
283
	 */
284
	private static function retainEncryptionKeys($file_path, $filename, $timestamp) {
285
		$size = 0;
286
287
		if (\OCP\App::isEnabled('files_encryption')) {
288
289
			$user = \OCP\User::getUser();
290
			$rootView = new \OC\Files\View('/');
291
292
			list($owner, $ownerPath) = self::getUidAndFilename($file_path);
293
294
			// file has been deleted in between
295
			if (empty($ownerPath)) {
296
				return 0;
297
			}
298
299
			$util = new \OCA\Files_Encryption\Util($rootView, $user);
300
301
			$baseDir = '/files_encryption/';
302
			if (!$util->isSystemWideMountPoint($ownerPath, $owner)) {
0 ignored issues
show
Bug introduced by
It seems like $owner defined by self::getUidAndFilename($file_path) on line 292 can also be of type false or null; however, OCA\Files_Encryption\Uti...sSystemWideMountPoint() 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...
303
				$baseDir = $owner . $baseDir;
304
			}
305
306
			$keyfiles = \OC\Files\Filesystem::normalizePath($baseDir . '/keys/' . $ownerPath);
307
308
			if ($rootView->is_dir($keyfiles)) {
309
				$size += self::calculateSize(new \OC\Files\View($keyfiles));
310 View Code Duplication
				if ($owner !== $user) {
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...
311
					self::copy_recursive($keyfiles, $owner . '/files_trashbin/keys/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
312
				}
313
				$rootView->rename($keyfiles, $user . '/files_trashbin/keys/' . $filename . '.d' . $timestamp);
314
			}
315
316
		}
317
		return $size;
318
	}
319
320
	/**
321
	 * restore files from trash bin
322
	 *
323
	 * @param string $file path to the deleted file
324
	 * @param string $filename name of the file
325
	 * @param int $timestamp time when the file was deleted
326
	 *
327
	 * @return bool
328
	 */
329
	public static function restore($file, $filename, $timestamp) {
330
331
		$user = \OCP\User::getUser();
332
		$view = new \OC\Files\View('/' . $user);
333
334
		$location = '';
335
		if ($timestamp) {
336
			$location = self::getLocation($user, $filename, $timestamp);
337
			if ($location === false) {
338
				\OC_Log::write('files_trashbin', 'trash bin database inconsistent!', \OC_Log::ERROR);
339
			} else {
340
				// if location no longer exists, restore file in the root directory
341
				if ($location !== '/' &&
342
					(!$view->is_dir('files/' . $location) ||
343
					!$view->isCreatable('files/' . $location))
344
				) {
345
					$location = '';
346
				}
347
			}
348
		}
349
350
		// we need a  extension in case a file/dir with the same name already exists
351
		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
352
353
		$source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file);
354
		$target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
355
		$mtime = $view->filemtime($source);
356
357
		// disable proxy to prevent recursive calls
358
		$proxyStatus = \OC_FileProxy::$enabled;
359
		\OC_FileProxy::$enabled = false;
360
361
		// restore file
362
		$restoreResult = $view->rename($source, $target);
363
364
		// handle the restore result
365
		if ($restoreResult) {
366
			$fakeRoot = $view->getRoot();
367
			$view->chroot('/' . $user . '/files');
368
			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
369
			$view->chroot($fakeRoot);
370
			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
371
				'trashPath' => \OC\Files\Filesystem::normalizePath($file)));
372
373
			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
374
			self::restoreEncryptionKeys($view, $file, $filename, $uniqueFilename, $location, $timestamp);
375
376
			if ($timestamp) {
377
				$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
378
				$query->execute(array($user, $filename, $timestamp));
379
			}
380
381
			// enable proxy
382
			\OC_FileProxy::$enabled = $proxyStatus;
383
384
			return true;
385
		}
386
387
		// enable proxy
388
		\OC_FileProxy::$enabled = $proxyStatus;
389
390
		return false;
391
	}
392
393
	/**
394
	 * restore versions from trash bin
395
	 *
396
	 * @param \OC\Files\View $view file view
397
	 * @param string $file complete path to file
398
	 * @param string $filename name of file once it was deleted
399
	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
400
	 * @param string $location location if file
401
	 * @param int $timestamp deletion time
402
	 * @return bool
403
	 */
404
	private static function restoreVersions(\OC\Files\View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
405
406
		if (\OCP\App::isEnabled('files_versions')) {
407
			// disable proxy to prevent recursive calls
408
			$proxyStatus = \OC_FileProxy::$enabled;
409
			\OC_FileProxy::$enabled = false;
410
411
			$user = \OCP\User::getUser();
412
			$rootView = new \OC\Files\View('/');
413
414
			$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
415
416
			list($owner, $ownerPath) = self::getUidAndFilename($target);
417
418
			// file has been deleted in between
419
			if (empty($ownerPath)) {
420
				\OC_FileProxy::$enabled = $proxyStatus;
421
				return false;
422
			}
423
424
			if ($timestamp) {
425
				$versionedFile = $filename;
426
			} else {
427
				$versionedFile = $file;
428
			}
429
430
			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
431
				$rootView->rename(\OC\Files\Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
432
			} else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp)) {
433
				foreach ($versions as $v) {
434
					if ($timestamp) {
435
						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
436
					} else {
437
						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
438
					}
439
				}
440
			}
441
442
			// enable proxy
443
			\OC_FileProxy::$enabled = $proxyStatus;
444
		}
445
	}
446
447
	/**
448
	 * restore encryption keys from trash bin
449
	 *
450
	 * @param \OC\Files\View $view
451
	 * @param string $file complete path to file
452
	 * @param string $filename name of file
453
	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
454
	 * @param string $location location of file
455
	 * @param int $timestamp deletion time
456
	 * @return bool
457
	 */
458
	private static function restoreEncryptionKeys(\OC\Files\View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
459
460
		if (\OCP\App::isEnabled('files_encryption')) {
461
			$user = \OCP\User::getUser();
462
			$rootView = new \OC\Files\View('/');
463
464
			$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
465
466
			list($owner, $ownerPath) = self::getUidAndFilename($target);
467
468
			// file has been deleted in between
469
			if (empty($ownerPath)) {
470
				return false;
471
			}
472
473
			$util = new \OCA\Files_Encryption\Util($rootView, $user);
474
475
			$baseDir = '/files_encryption/';
476
			if (!$util->isSystemWideMountPoint($ownerPath, $owner)) {
0 ignored issues
show
Bug introduced by
It seems like $owner defined by self::getUidAndFilename($target) on line 466 can also be of type false or null; however, OCA\Files_Encryption\Uti...sSystemWideMountPoint() 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...
477
				$baseDir = $owner . $baseDir;
478
			}
479
480
			$source_location = dirname($file);
481
482
			if ($view->is_dir('/files_trashbin/keys/' . $file)) {
483
				if ($source_location != '.') {
484
					$keyfile = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/keys/' . $source_location . '/' . $filename);
485
				} else {
486
					$keyfile = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/keys/' . $filename);
487
				}
488
			}
489
490
			if ($timestamp) {
491
				$keyfile .= '.d' . $timestamp;
0 ignored issues
show
Bug introduced by
The variable $keyfile 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...
492
			}
493
494
			if ($rootView->is_dir($keyfile)) {
495
				$rootView->rename($keyfile, $baseDir . '/keys/' . $ownerPath);
496
			}
497
498
		}
499
	}
500
501
	/**
502
	 * delete all files from the trash
503
	 */
504
	public static function deleteAll() {
505
		$user = \OCP\User::getUser();
506
		$view = new \OC\Files\View('/' . $user);
507
		$view->deleteAll('files_trashbin');
508
		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
509
		$query->execute(array($user));
510
511
		return true;
512
	}
513
514
515
	/**
516
	 * delete file from trash bin permanently
517
	 *
518
	 * @param string $filename path to the file
519
	 * @param string $user
520
	 * @param int $timestamp of deletion time
521
	 *
522
	 * @return int size of deleted files
523
	 */
524
	public static function delete($filename, $user, $timestamp = null) {
525
		$view = new \OC\Files\View('/' . $user);
526
		$size = 0;
527
528
		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...
529
			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
530
			$query->execute(array($user, $filename, $timestamp));
531
			$file = $filename . '.d' . $timestamp;
532
		} else {
533
			$file = $filename;
534
		}
535
536
		$size += self::deleteVersions($view, $file, $filename, $timestamp);
537
		$size += self::deleteEncryptionKeys($view, $file, $filename, $timestamp);
538
539
		if ($view->is_dir('/files_trashbin/files/' . $file)) {
540
			$size += self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin/files/' . $file));
541
		} else {
542
			$size += $view->filesize('/files_trashbin/files/' . $file);
543
		}
544
		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', array('path' => '/files_trashbin/files/' . $file));
545
		$view->unlink('/files_trashbin/files/' . $file);
546
		\OC_Hook::emit('\OCP\Trashbin', 'delete', array('path' => '/files_trashbin/files/' . $file));
547
548
		return $size;
549
	}
550
551
	/**
552
	 * @param \OC\Files\View $view
553
	 * @param $file
554
	 * @param $filename
555
	 * @param $timestamp
556
	 * @return int
557
	 */
558
	private static function deleteVersions(\OC\Files\View $view, $file, $filename, $timestamp) {
559
		$size = 0;
560
		if (\OCP\App::isEnabled('files_versions')) {
561
			$user = \OCP\User::getUser();
562
			if ($view->is_dir('files_trashbin/versions/' . $file)) {
563
				$size += self::calculateSize(new \OC\Files\view('/' . $user . '/files_trashbin/versions/' . $file));
564
				$view->unlink('files_trashbin/versions/' . $file);
565
			} else if ($versions = self::getVersionsFromTrash($filename, $timestamp)) {
566
				foreach ($versions as $v) {
567
					if ($timestamp) {
568
						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
569
						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
570
					} else {
571
						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
572
						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
573
					}
574
				}
575
			}
576
		}
577
		return $size;
578
	}
579
580
	/**
581
	 * @param \OC\Files\View $view
582
	 * @param $file
583
	 * @param $filename
584
	 * @param $timestamp
585
	 * @return int
586
	 */
587
	private static function deleteEncryptionKeys(\OC\Files\View $view, $file, $filename, $timestamp) {
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
588
		$size = 0;
589
		if (\OCP\App::isEnabled('files_encryption')) {
590
			$user = \OCP\User::getUser();
591
592
			$keyfiles = \OC\Files\Filesystem::normalizePath('files_trashbin/keys/' . $filename);
593
594
			if ($timestamp) {
595
				$keyfiles .= '.d' . $timestamp;
596
			}
597
			if ($view->is_dir($keyfiles)) {
598
				$size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfiles));
599
				$view->deleteAll($keyfiles);
600
601
			}
602
		}
603
		return $size;
604
	}
605
606
	/**
607
	 * check to see whether a file exists in trashbin
608
	 *
609
	 * @param string $filename path to the file
610
	 * @param int $timestamp of deletion time
611
	 * @return bool true if file exists, otherwise false
612
	 */
613
	public static function file_exists($filename, $timestamp = null) {
614
		$user = \OCP\User::getUser();
615
		$view = new \OC\Files\View('/' . $user);
616
617
		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...
618
			$filename = $filename . '.d' . $timestamp;
619
		} else {
620
			$filename = $filename;
0 ignored issues
show
Bug introduced by
Why assign $filename to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
621
		}
622
623
		$target = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $filename);
624
		return $view->file_exists($target);
625
	}
626
627
	/**
628
	 * deletes used space for trash bin in db if user was deleted
629
	 *
630
	 * @param string $uid id of deleted user
631
	 * @return bool result of db delete operation
632
	 */
633
	public static function deleteUser($uid) {
634
		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
635
		return $query->execute(array($uid));
636
	}
637
638
	/**
639
	 * calculate remaining free space for trash bin
640
	 *
641
	 * @param integer $trashbinSize current size of the trash bin
642
	 * @param string $user
643
	 * @return int available free space for trash bin
644
	 */
645
	private static function calculateFreeSpace($trashbinSize, $user) {
646
		$config = \OC::$server->getConfig();
647
648
		$softQuota = true;
649
		$quota = $config->getUserValue($user, 'files', 'quota', null);
650
		$view = new \OC\Files\View('/' . $user);
651 View Code Duplication
		if ($quota === null || $quota === 'default') {
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...
652
			$quota = $config->getAppValue('files', 'default_quota', null);
653
		}
654
		if ($quota === null || $quota === 'none') {
655
			$quota = \OC\Files\Filesystem::free_space('/');
656
			$softQuota = false;
657
			if ($quota === \OCP\Files\FileInfo::SPACE_UNKNOWN) {
658
				$quota = 0;
659
			}
660
		} else {
661
			$quota = \OCP\Util::computerFileSize($quota);
662
		}
663
664
		// calculate available space for trash bin
665
		// subtract size of files and current trash bin size from quota
666
		if ($softQuota) {
667
			$rootInfo = $view->getFileInfo('/files/', false);
668
			$free = $quota - $rootInfo['size']; // remaining free space for user
669
			if ($free > 0) {
670
				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
671
			} else {
672
				$availableSpace = $free - $trashbinSize;
673
			}
674
		} else {
675
			$availableSpace = $quota;
676
		}
677
678
		return $availableSpace;
679
	}
680
681
	/**
682
	 * resize trash bin if necessary after a new file was added to ownCloud
683
	 * @param string $user user id
684
	 */
685
	public static function resizeTrash($user) {
686
687
		$size = self::getTrashbinSize($user);
688
689
		$freeSpace = self::calculateFreeSpace($size, $user);
690
691
		if ($freeSpace < 0) {
692
			self::expire($size, $user);
693
		}
694
	}
695
696
	/**
697
	 * clean up the trash bin
698
	 *
699
	 * @param int $trashbinSize current size of the trash bin
700
	 * @param string $user
701
	 * @return int size of expired files
702
	 */
703
	private static function expire($trashbinSize, $user) {
704
705
		// let the admin disable auto expire
706
		$autoExpire = \OC_Config::getValue('trashbin_auto_expire', true);
707
		if ($autoExpire === false) {
708
			return 0;
709
		}
710
711
		$availableSpace = self::calculateFreeSpace($trashbinSize, $user);
712
		$size = 0;
713
714
		$retention_obligation = \OC_Config::getValue('trashbin_retention_obligation', self::DEFAULT_RETENTION_OBLIGATION);
715
716
		$limit = time() - ($retention_obligation * 86400);
717
718
		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
719
720
		// delete all files older then $retention_obligation
721
		list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user, $limit, $retention_obligation);
722
723
		$size += $delSize;
724
		$availableSpace += $size;
725
726
		// delete files from trash until we meet the trash bin size limit again
727
		$size += self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
728
729
		return $size;
730
	}
731
732
	/**
733
	 * if the size limit for the trash bin is reached, we delete the oldest
734
	 * files in the trash bin until we meet the limit again
735
	 * @param array $files
736
	 * @param string $user
737
	 * @param int $availableSpace available disc space
738
	 * @return int size of deleted files
739
	 */
740
	protected static function deleteFiles($files, $user, $availableSpace) {
741
		$size = 0;
742
743
		if ($availableSpace < 0) {
744
			foreach ($files as $file) {
745
				if ($availableSpace < 0) {
746
					$tmp = self::delete($file['name'], $user, $file['mtime']);
747
					\OC_Log::write('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', \OC_log::INFO);
748
					$availableSpace += $tmp;
749
					$size += $tmp;
750
				} else {
751
					break;
752
				}
753
			}
754
		}
755
		return $size;
756
	}
757
758
	/**
759
	 * delete files older then max storage time
760
	 *
761
	 * @param array $files list of files sorted by mtime
762
	 * @param string $user
763
	 * @param int $limit files older then limit should be deleted
764
	 * @param int $retention_obligation max age of file in days
765
	 * @return array size of deleted files and number of deleted files
766
	 */
767
	protected static function deleteExpiredFiles($files, $user, $limit, $retention_obligation) {
768
		$size = 0;
769
		$count = 0;
770
		foreach ($files as $file) {
771
			$timestamp = $file['mtime'];
772
			$filename = $file['name'];
773
			if ($timestamp <= $limit) {
774
				$count++;
775
				$size += self::delete($filename, $user, $timestamp);
776
				\OC_Log::write('files_trashbin', 'remove "' . $filename . '" from trash bin because it is older than ' . $retention_obligation, \OC_log::INFO);
777
			} else {
778
				break;
779
			}
780
		}
781
782
		return array($size, $count);
783
	}
784
785
	/**
786
	 * recursive copy to copy a whole directory
787
	 *
788
	 * @param string $source source path, relative to the users files directory
789
	 * @param string $destination destination path relative to the users root directoy
790
	 * @param \OC\Files\View $view file view for the users root directory
791
	 * @return int
792
	 * @throws Exceptions\CopyRecursiveException
793
	 */
794
	private static function copy_recursive($source, $destination, \OC\Files\View $view) {
795
		$size = 0;
796
		if ($view->is_dir($source)) {
797
			$view->mkdir($destination);
798
			$view->touch($destination, $view->filemtime($source));
799
			foreach ($view->getDirectoryContent($source) as $i) {
800
				$pathDir = $source . '/' . $i['name'];
801
				if ($view->is_dir($pathDir)) {
802
					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
803
				} else {
804
					$size += $view->filesize($pathDir);
805
					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
806
					if (!$result) {
807
						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
808
					}
809
					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
810
				}
811
			}
812
		} else {
813
			$size += $view->filesize($source);
814
			$result = $view->copy($source, $destination);
815
			if (!$result) {
816
				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
817
			}
818
			$view->touch($destination, $view->filemtime($source));
819
		}
820
		return $size;
821
	}
822
823
	/**
824
	 * find all versions which belong to the file we want to restore
825
	 *
826
	 * @param string $filename name of the file which should be restored
827
	 * @param int $timestamp timestamp when the file was deleted
828
	 * @return array
829
	 */
830
	private static function getVersionsFromTrash($filename, $timestamp) {
831
		$view = new \OC\Files\View('/' . \OCP\User::getUser() . '/files_trashbin/versions');
832
		$versions = array();
833
834
		//force rescan of versions, local storage may not have updated the cache
835
		if (!self::$scannedVersions) {
836
			/** @var \OC\Files\Storage\Storage $storage */
837
			list($storage, ) = $view->resolvePath('/');
838
			$storage->getScanner()->scan('files_trashbin/versions');
839
			self::$scannedVersions = true;
840
		}
841
842
		if ($timestamp) {
843
			// fetch for old versions
844
			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
845
			$offset = -strlen($timestamp) - 2;
846
		} else {
847
			$matches = $view->searchRaw($filename . '.v%');
848
		}
849
850
		if (is_array($matches)) {
851
			foreach ($matches as $ma) {
852
				if ($timestamp) {
853
					$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...
854
					$versions[] = (end($parts));
855
				} else {
856
					$parts = explode('.v', $ma);
857
					$versions[] = (end($parts));
858
				}
859
			}
860
		}
861
		return $versions;
862
	}
863
864
	/**
865
	 * find unique extension for restored file if a file with the same name already exists
866
	 *
867
	 * @param string $location where the file should be restored
868
	 * @param string $filename name of the file
869
	 * @param \OC\Files\View $view filesystem view relative to users root directory
870
	 * @return string with unique extension
871
	 */
872
	private static function getUniqueFilename($location, $filename, \OC\Files\View $view) {
873
		$ext = pathinfo($filename, PATHINFO_EXTENSION);
874
		$name = pathinfo($filename, PATHINFO_FILENAME);
875
		$l = \OC::$server->getL10N('files_trashbin');
876
877
		// if extension is not empty we set a dot in front of it
878
		if ($ext !== '') {
879
			$ext = '.' . $ext;
880
		}
881
882
		if ($view->file_exists('files' . $location . '/' . $filename)) {
883
			$i = 2;
884
			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
885
			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
886
				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
887
				$i++;
888
			}
889
890
			return $uniqueName;
891
		}
892
893
		return $filename;
894
	}
895
896
	/**
897
	 * get the size from a given root folder
898
	 * @param \OC\Files\View $view file view on the root folder
899
	 * @return integer size of the folder
900
	 */
901
	private static function calculateSize($view) {
902
		$root = \OC::$server->getConfig()->getSystemValue('datadirectory') . $view->getAbsolutePath('');
903
		if (!file_exists($root)) {
904
			return 0;
905
		}
906
		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
907
		$size = 0;
908
909
                /**
910
		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
911
		 * This bug is fixed in PHP 5.5.9 or before
912
		 * See #8376
913
		 */
914
		$iterator->rewind();
915
		while ($iterator->valid()) {
916
			$path = $iterator->current();
917
			$relpath = substr($path, strlen($root) - 1);
918
			if (!$view->is_dir($relpath)) {
919
				$size += $view->filesize($relpath);
920
			}
921
			$iterator->next();
922
		}
923
		return $size;
924
	}
925
926
	/**
927
	 * get current size of trash bin from a given user
928
	 *
929
	 * @param string $user user who owns the trash bin
930
	 * @return integer trash bin size
931
	 */
932
	private static function getTrashbinSize($user) {
933
		$view = new \OC\Files\View('/' . $user);
934
		$fileInfo = $view->getFileInfo('/files_trashbin');
935
		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
936
	}
937
938
	/**
939
	 * register hooks
940
	 */
941
	public static function registerHooks() {
942
		// create storage wrapper on setup
943
		\OCP\Util::connectHook('OC_Filesystem', 'setup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
944
		//Listen to delete user signal
945
		\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
946
		//Listen to post write hook
947
		\OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
948
		// pre and post-rename, disable trash logic for the copy+unlink case
949
		\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
950
		\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
951
	}
952
953
	/**
954
	 * check if trash bin is empty for a given user
955
	 * @param string $user
956
	 * @return bool
957
	 */
958
	public static function isEmpty($user) {
959
960
		$view = new \OC\Files\View('/' . $user . '/files_trashbin');
961
		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
962
			while ($file = readdir($dh)) {
963
				if ($file !== '.' and $file !== '..') {
964
					return false;
965
				}
966
			}
967
		}
968
		return true;
969
	}
970
971
	/**
972
	 * @param $path
973
	 * @return string
974
	 */
975
	public static function preview_icon($path) {
976
		return \OC_Helper::linkToRoute('core_ajax_trashbin_preview', array('x' => 36, 'y' => 36, 'file' => $path));
977
	}
978
}
979