Completed
Push — master ( 96d4e1...12c5c3 )
by Morris
31:41 queued 21:00
created

OC_App   F

Complexity

Total Complexity 234

Size/Duplication

Total Lines 1280
Duplicated Lines 6.8 %

Coupling/Cohesion

Components 4
Dependencies 31

Importance

Changes 0
Metric Value
dl 87
loc 1280
rs 0.5217
c 0
b 0
f 0
wmc 234
lcom 4
cbo 31

49 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanAppId() 0 3 1
A isAppLoaded() 0 3 1
C loadApps() 0 26 8
B getInstallPath() 0 14 5
D findAppInDirectories() 0 40 10
A getAppPath() 0 10 4
A getAppWebPath() 0 6 2
A checkAppDependencies() 0 12 2
D loadApp() 15 40 10
A registerAutoloading() 0 17 4
A requireAppFile() 0 12 3
A isType() 0 12 4
A getAppTypes() 0 12 3
B setAppTypes() 0 22 6
A isShipped() 0 3 1
B getEnabledApps() 0 25 4
A isEnabled() 0 3 1
B enable() 5 59 8
A removeApp() 0 13 2
A disable() 0 17 2
C proceedNavigation() 14 46 14
B proceedAppNavigation() 14 31 5
A getAppVersion() 0 9 4
A getAppVersionByPath() 0 5 2
D getAppInfo() 0 33 10
A getNavigation() 0 4 1
A getHeaderNavigation() 0 4 1
A getSettingsNavigation() 0 4 1
A getCurrentApp() 0 17 4
A getForms() 0 17 4
A registerAdmin() 0 3 1
A registerPersonal() 0 3 1
A registerLogIn() 0 3 1
A getAlternativeLogIns() 0 3 1
C getAllApps() 0 24 8
C listAllApps() 0 79 16
A getInternalAppIdByOcs() 0 9 3
A shouldUpgrade() 0 11 4
A adjustVersionParts() 0 13 3
D isAppCompatible() 17 40 13
A getAppVersions() 0 9 2
C installApp() 5 48 8
C updateApp() 11 49 10
B executeRepairSteps() 0 22 4
A setupBackgroundJobs() 0 6 2
A setupLiveMigrations() 0 8 2
A getStorage() 0 17 4
C findBestL10NOption() 0 44 12
B parseAppInfo() 6 18 12

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 OC_App 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 OC_App, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Björn Schießle <[email protected]>
10
 * @author Borjan Tchakaloff <[email protected]>
11
 * @author Brice Maron <[email protected]>
12
 * @author Christopher Schäpers <[email protected]>
13
 * @author Felix Moeller <[email protected]>
14
 * @author Frank Karlitschek <[email protected]>
15
 * @author Georg Ehrke <[email protected]>
16
 * @author Jakob Sack <[email protected]>
17
 * @author Jan-Christoph Borchardt <[email protected]>
18
 * @author Joas Schilling <[email protected]>
19
 * @author Jörn Friedrich Dreyer <[email protected]>
20
 * @author Kamil Domanski <[email protected]>
21
 * @author Klaas Freitag <[email protected]>
22
 * @author Lukas Reschke <[email protected]>
23
 * @author Markus Goetz <[email protected]>
24
 * @author Morris Jobke <[email protected]>
25
 * @author RealRancor <[email protected]>
26
 * @author Robin Appelman <[email protected]>
27
 * @author Robin McCorkell <[email protected]>
28
 * @author Roeland Jago Douma <[email protected]>
29
 * @author Sam Tuke <[email protected]>
30
 * @author Thomas Müller <[email protected]>
31
 * @author Thomas Tanghus <[email protected]>
32
 * @author Tom Needham <[email protected]>
33
 * @author Vincent Petry <[email protected]>
34
 *
35
 * @license AGPL-3.0
36
 *
37
 * This code is free software: you can redistribute it and/or modify
38
 * it under the terms of the GNU Affero General Public License, version 3,
39
 * as published by the Free Software Foundation.
40
 *
41
 * This program is distributed in the hope that it will be useful,
42
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44
 * GNU Affero General Public License for more details.
45
 *
46
 * You should have received a copy of the GNU Affero General Public License, version 3,
47
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
48
 *
49
 */
50
use OC\App\DependencyAnalyzer;
51
use OC\App\InfoParser;
52
use OC\App\Platform;
53
use OC\Installer;
54
use OC\OCSClient;
55
use OC\Repair;
56
use OCP\App\ManagerEvent;
57
58
/**
59
 * This class manages the apps. It allows them to register and integrate in the
60
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
61
 * upgrading and removing apps.
62
 */
63
class OC_App {
64
	static private $appVersion = [];
65
	static private $adminForms = array();
66
	static private $personalForms = array();
67
	static private $appInfo = array();
68
	static private $appTypes = array();
69
	static private $loadedApps = array();
70
	static private $altLogin = array();
71
	static private $alreadyRegistered = [];
72
	const officialApp = 200;
73
74
	/**
75
	 * clean the appId
76
	 *
77
	 * @param string|boolean $app AppId that needs to be cleaned
78
	 * @return string
79
	 */
80
	public static function cleanAppId($app) {
81
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
82
	}
83
84
	/**
85
	 * Check if an app is loaded
86
	 *
87
	 * @param string $app
88
	 * @return bool
89
	 */
90
	public static function isAppLoaded($app) {
91
		return in_array($app, self::$loadedApps, true);
92
	}
93
94
	/**
95
	 * loads all apps
96
	 *
97
	 * @param string[] | string | null $types
98
	 * @return bool
99
	 *
100
	 * This function walks through the ownCloud directory and loads all apps
101
	 * it can find. A directory contains an app if the file /appinfo/info.xml
102
	 * exists.
103
	 *
104
	 * if $types is set, only apps of those types will be loaded
105
	 */
106
	public static function loadApps($types = null) {
107
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
108
			return false;
109
		}
110
		// Load the enabled apps here
111
		$apps = self::getEnabledApps();
112
113
		// Add each apps' folder as allowed class path
114
		foreach($apps as $app) {
115
			$path = self::getAppPath($app);
116
			if($path !== false) {
117
				self::registerAutoloading($app, $path);
118
			}
119
		}
120
121
		// prevent app.php from printing output
122
		ob_start();
123
		foreach ($apps as $app) {
124
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
125
				self::loadApp($app);
126
			}
127
		}
128
		ob_end_clean();
129
130
		return true;
131
	}
132
133
	/**
134
	 * load a single app
135
	 *
136
	 * @param string $app
137
	 */
138
	public static function loadApp($app) {
139
		self::$loadedApps[] = $app;
140
		$appPath = self::getAppPath($app);
141
		if($appPath === false) {
142
			return;
143
		}
144
145
		// in case someone calls loadApp() directly
146
		self::registerAutoloading($app, $appPath);
147
148
		if (is_file($appPath . '/appinfo/app.php')) {
149
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
150
			self::requireAppFile($app);
151
			if (self::isType($app, array('authentication'))) {
152
				// since authentication apps affect the "is app enabled for group" check,
153
				// the enabled apps cache needs to be cleared to make sure that the
154
				// next time getEnableApps() is called it will also include apps that were
155
				// enabled for groups
156
				self::$enabledAppsCache = array();
157
			}
158
			\OC::$server->getEventLogger()->end('load_app_' . $app);
159
		}
160
161
		$info = self::getAppInfo($app);
162 View Code Duplication
		if (!empty($info['activity']['filters'])) {
163
			foreach ($info['activity']['filters'] as $filter) {
164
				\OC::$server->getActivityManager()->registerFilter($filter);
165
			}
166
		}
167 View Code Duplication
		if (!empty($info['activity']['settings'])) {
168
			foreach ($info['activity']['settings'] as $setting) {
169
				\OC::$server->getActivityManager()->registerSetting($setting);
170
			}
171
		}
172 View Code Duplication
		if (!empty($info['activity']['providers'])) {
173
			foreach ($info['activity']['providers'] as $provider) {
174
				\OC::$server->getActivityManager()->registerProvider($provider);
175
			}
176
		}
177
	}
178
179
	/**
180
	 * @internal
181
	 * @param string $app
182
	 * @param string $path
183
	 */
184
	public static function registerAutoloading($app, $path) {
185
		$key = $app . '-' . $path;
186
		if(isset(self::$alreadyRegistered[$key])) {
187
			return;
188
		}
189
		self::$alreadyRegistered[$key] = true;
190
		// Register on PSR-4 composer autoloader
191
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
192
		\OC::$server->registerNamespace($app, $appNamespace);
193
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
194
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
195
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
196
		}
197
198
		// Register on legacy autoloader
199
		\OC::$loader->addValidRoot($path);
200
	}
201
202
	/**
203
	 * Load app.php from the given app
204
	 *
205
	 * @param string $app app name
206
	 */
207
	private static function requireAppFile($app) {
208
		try {
209
			// encapsulated here to avoid variable scope conflicts
210
			require_once $app . '/appinfo/app.php';
211
		} catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
212
			\OC::$server->getLogger()->logException($ex);
213
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
214
			if (!in_array($app, $blacklist)) {
215
				self::disable($app);
216
			}
217
		}
218
	}
219
220
	/**
221
	 * check if an app is of a specific type
222
	 *
223
	 * @param string $app
224
	 * @param string|array $types
225
	 * @return bool
226
	 */
227
	public static function isType($app, $types) {
228
		if (is_string($types)) {
229
			$types = array($types);
230
		}
231
		$appTypes = self::getAppTypes($app);
232
		foreach ($types as $type) {
233
			if (array_search($type, $appTypes) !== false) {
234
				return true;
235
			}
236
		}
237
		return false;
238
	}
239
240
	/**
241
	 * get the types of an app
242
	 *
243
	 * @param string $app
244
	 * @return array
245
	 */
246
	private static function getAppTypes($app) {
247
		//load the cache
248
		if (count(self::$appTypes) == 0) {
249
			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
0 ignored issues
show
Documentation Bug introduced by
It seems like \OC::$server->getAppConf...tValues(false, 'types') can also be of type false. However, the property $appTypes is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
250
		}
251
252
		if (isset(self::$appTypes[$app])) {
253
			return explode(',', self::$appTypes[$app]);
254
		} else {
255
			return array();
256
		}
257
	}
258
259
	/**
260
	 * read app types from info.xml and cache them in the database
261
	 */
262
	public static function setAppTypes($app) {
263
		$appData = self::getAppInfo($app);
264
		if(!is_array($appData)) {
265
			return;
266
		}
267
268
		if (isset($appData['types'])) {
269
			$appTypes = implode(',', $appData['types']);
270
		} else {
271
			$appTypes = '';
272
			$appData['types'] = [];
273
		}
274
275
		\OC::$server->getAppConfig()->setValue($app, 'types', $appTypes);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
276
277
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
278
			$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'yes');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::getValue() has been deprecated with message: 8.0.0 use method getAppValue of \OCP\IConfig This function gets a value from the appconfig table. If the key does
not exist the default value will be returned

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
279
			if ($enabled !== 'yes' && $enabled !== 'no') {
280
				\OC::$server->getAppConfig()->setValue($app, 'enabled', 'yes');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
281
			}
282
		}
283
	}
284
285
	/**
286
	 * check if app is shipped
287
	 *
288
	 * @param string $appId the id of the app to check
289
	 * @return bool
290
	 *
291
	 * Check if an app that is installed is a shipped app or installed from the appstore.
292
	 */
293
	public static function isShipped($appId) {
294
		return \OC::$server->getAppManager()->isShipped($appId);
295
	}
296
297
	/**
298
	 * get all enabled apps
299
	 */
300
	protected static $enabledAppsCache = array();
301
302
	/**
303
	 * Returns apps enabled for the current user.
304
	 *
305
	 * @param bool $forceRefresh whether to refresh the cache
306
	 * @param bool $all whether to return apps for all users, not only the
307
	 * currently logged in one
308
	 * @return string[]
309
	 */
310
	public static function getEnabledApps($forceRefresh = false, $all = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $forceRefresh 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...
311
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
312
			return array();
313
		}
314
		// in incognito mode or when logged out, $user will be false,
315
		// which is also the case during an upgrade
316
		$appManager = \OC::$server->getAppManager();
317
		if ($all) {
318
			$user = null;
319
		} else {
320
			$user = \OC::$server->getUserSession()->getUser();
321
		}
322
323
		if (is_null($user)) {
324
			$apps = $appManager->getInstalledApps();
325
		} else {
326
			$apps = $appManager->getEnabledAppsForUser($user);
327
		}
328
		$apps = array_filter($apps, function ($app) {
329
			return $app !== 'files';//we add this manually
330
		});
331
		sort($apps);
332
		array_unshift($apps, 'files');
333
		return $apps;
334
	}
335
336
	/**
337
	 * checks whether or not an app is enabled
338
	 *
339
	 * @param string $app app
340
	 * @return bool
341
	 *
342
	 * This function checks whether or not an app is enabled.
343
	 */
344
	public static function isEnabled($app) {
345
		return \OC::$server->getAppManager()->isEnabledForUser($app);
346
	}
347
348
	/**
349
	 * enables an app
350
	 *
351
	 * @param string $appId
352
	 * @param array $groups (optional) when set, only these groups will have access to the app
353
	 * @throws \Exception
354
	 * @return void
355
	 *
356
	 * This function set an app as enabled in appconfig.
357
	 */
358
	public function enable($appId,
359
						   $groups = null) {
360
		self::$enabledAppsCache = []; // flush
361
		$l = \OC::$server->getL10N('core');
362
		$config = \OC::$server->getConfig();
363
364
		// Check if app is already downloaded
365
		$installer = new Installer(
366
			\OC::$server->getAppFetcher(),
367
			\OC::$server->getHTTPClientService(),
368
			\OC::$server->getTempManager(),
369
			\OC::$server->getLogger()
370
		);
371
		$isDownloaded = $installer->isDownloaded($appId);
372
373
		if(!$isDownloaded) {
374
			$installer->downloadApp($appId);
375
		}
376
377
		if (!Installer::isInstalled($appId)) {
378
			$appId = self::installApp(
379
				$appId,
380
				$config,
381
				$l
382
			);
383
			$appPath = self::getAppPath($appId);
384
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 383 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
385
			$installer->installApp($appId);
386
		} else {
387
			// check for required dependencies
388
			$info = self::getAppInfo($appId);
389
			self::checkAppDependencies($config, $l, $info);
0 ignored issues
show
Bug introduced by
It seems like $info defined by self::getAppInfo($appId) on line 388 can also be of type null; however, OC_App::checkAppDependencies() does only seem to accept array, 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...
390
			$appPath = self::getAppPath($appId);
391
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 390 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
392
			$installer->installApp($appId);
393
		}
394
395
		$appManager = \OC::$server->getAppManager();
396
		if (!is_null($groups)) {
397
			$groupManager = \OC::$server->getGroupManager();
398
			$groupsList = [];
399
			foreach ($groups as $group) {
400
				$groupItem = $groupManager->get($group);
401
				if ($groupItem instanceof \OCP\IGroup) {
402
					$groupsList[] = $groupManager->get($group);
403
				}
404
			}
405
			$appManager->enableAppForGroups($appId, $groupsList);
406
		} else {
407
			$appManager->enableApp($appId);
408
		}
409
410
		$info = self::getAppInfo($appId);
411 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
412
			$appPath = self::getAppPath($appId);
413
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 412 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
414
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
415
		}
416
	}
417
418
	/**
419
	 * @param string $app
420
	 * @return bool
421
	 */
422
	public static function removeApp($app) {
423
		if (self::isShipped($app)) {
424
			return false;
425
		}
426
427
		$installer = new Installer(
428
			\OC::$server->getAppFetcher(),
429
			\OC::$server->getHTTPClientService(),
430
			\OC::$server->getTempManager(),
431
			\OC::$server->getLogger()
432
		);
433
		return $installer->removeApp($app);
434
	}
435
436
	/**
437
	 * This function set an app as disabled in appconfig.
438
	 *
439
	 * @param string $app app
440
	 * @throws Exception
441
	 */
442
	public static function disable($app) {
443
		// flush
444
		self::$enabledAppsCache = array();
445
446
		// run uninstall steps
447
		$appData = OC_App::getAppInfo($app);
448
		if (!is_null($appData)) {
449
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
450
		}
451
452
		// emit disable hook - needed anymore ?
453
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
454
455
		// finally disable it
456
		$appManager = \OC::$server->getAppManager();
457
		$appManager->disableApp($app);
458
	}
459
460
	// This is private as well. It simply works, so don't ask for more details
461
	private static function proceedNavigation($list) {
462
		usort($list, function($a, $b) {
463
			if (isset($a['order']) && isset($b['order'])) {
464
				return ($a['order'] < $b['order']) ? -1 : 1;
465
			} else if (isset($a['order']) || isset($b['order'])) {
466
				return isset($a['order']) ? -1 : 1;
467
			} else {
468
				return ($a['name'] < $b['name']) ? -1 : 1;
469
			}
470
		});
471
472
		$activeAppIndex = -1;
473
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
474 View Code Duplication
		foreach ($list as $index => &$navEntry) {
475
			if ($navEntry['id'] == $activeApp) {
476
				$navEntry['active'] = true;
477
				$activeAppIndex = $index;
478
			} else {
479
				$navEntry['active'] = false;
480
			}
481
		}
482
		unset($navEntry);
483
484
		if (count($list) <= 8) {
485
			return $list;
486
		}
487
488
		$headerIconCount = 7;
489 View Code Duplication
		if($activeAppIndex > ($headerIconCount-1)) {
490
			$active = $list[$activeAppIndex];
491
			$lastInHeader = $list[$headerIconCount-1];
492
			$list[$headerIconCount-1] = $active;
493
			$list[$activeAppIndex] = $lastInHeader;
494
		}
495
496
		foreach ($list as $index => &$navEntry) {
497
			$navEntry['showInHeader'] = false;
498
			if($index < $headerIconCount) {
499
				$navEntry['showInHeader'] = true;
500
			}
501
		}
502
503
504
505
		return $list;
506
	}
507
508
	public static function proceedAppNavigation($entries) {
509
		$activeAppIndex = -1;
510
		$list = self::proceedNavigation($entries);
511
512
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
513 View Code Duplication
		foreach ($list as $index => &$navEntry) {
514
			if ($navEntry['id'] == $activeApp) {
515
				$navEntry['active'] = true;
516
				$activeAppIndex = $index;
517
			} else {
518
				$navEntry['active'] = false;
519
			}
520
		}
521
522
523
		if (count($list) <= 8) {
524
			return $list;
525
		}
526
527
		$headerIconCount = 7;
528
		// move active item to last position
529 View Code Duplication
		if($activeAppIndex > ($headerIconCount-1)) {
530
			$active = $list[$activeAppIndex];
531
			$lastInHeader = $list[$headerIconCount-1];
532
			$list[$headerIconCount-1] = $active;
533
			$list[$activeAppIndex] = $lastInHeader;
534
		}
535
		$list = array_slice($list, 0, $headerIconCount);
536
537
		return $list;
538
	}
539
540
	/**
541
	 * Get the path where to install apps
542
	 *
543
	 * @return string|false
544
	 */
545
	public static function getInstallPath() {
546
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
547
			return false;
548
		}
549
550
		foreach (OC::$APPSROOTS as $dir) {
551
			if (isset($dir['writable']) && $dir['writable'] === true) {
552
				return $dir['path'];
553
			}
554
		}
555
556
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
557
		return null;
558
	}
559
560
561
	/**
562
	 * search for an app in all app-directories
563
	 *
564
	 * @param string $appId
565
	 * @return false|string
566
	 */
567
	public static function findAppInDirectories($appId) {
568
		$sanitizedAppId = self::cleanAppId($appId);
569
		if($sanitizedAppId !== $appId) {
570
			return false;
571
		}
572
		static $app_dir = array();
573
574
		if (isset($app_dir[$appId])) {
575
			return $app_dir[$appId];
576
		}
577
578
		$possibleApps = array();
579
		foreach (OC::$APPSROOTS as $dir) {
580
			if (file_exists($dir['path'] . '/' . $appId)) {
581
				$possibleApps[] = $dir;
582
			}
583
		}
584
585
		if (empty($possibleApps)) {
586
			return false;
587
		} elseif (count($possibleApps) === 1) {
588
			$dir = array_shift($possibleApps);
589
			$app_dir[$appId] = $dir;
590
			return $dir;
591
		} else {
592
			$versionToLoad = array();
593
			foreach ($possibleApps as $possibleApp) {
594
				$version = self::getAppVersionByPath($possibleApp['path']);
595
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
596
					$versionToLoad = array(
597
						'dir' => $possibleApp,
598
						'version' => $version,
599
					);
600
				}
601
			}
602
			$app_dir[$appId] = $versionToLoad['dir'];
603
			return $versionToLoad['dir'];
604
			//TODO - write test
605
		}
606
	}
607
608
	/**
609
	 * Get the directory for the given app.
610
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
611
	 *
612
	 * @param string $appId
613
	 * @return string|false
614
	 */
615
	public static function getAppPath($appId) {
616
		if ($appId === null || trim($appId) === '') {
617
			return false;
618
		}
619
620
		if (($dir = self::findAppInDirectories($appId)) != false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $dir = self::findAppInDirectories($appId) of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
621
			return $dir['path'] . '/' . $appId;
622
		}
623
		return false;
624
	}
625
626
	/**
627
	 * Get the path for the given app on the access
628
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
629
	 *
630
	 * @param string $appId
631
	 * @return string|false
632
	 */
633
	public static function getAppWebPath($appId) {
634
		if (($dir = self::findAppInDirectories($appId)) != false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $dir = self::findAppInDirectories($appId) of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
635
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
636
		}
637
		return false;
638
	}
639
640
	/**
641
	 * get the last version of the app from appinfo/info.xml
642
	 *
643
	 * @param string $appId
644
	 * @param bool $useCache
645
	 * @return string
646
	 */
647
	public static function getAppVersion($appId, $useCache = true) {
648
		if($useCache && isset(self::$appVersion[$appId])) {
649
			return self::$appVersion[$appId];
650
		}
651
652
		$file = self::getAppPath($appId);
653
		self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
654
		return self::$appVersion[$appId];
655
	}
656
657
	/**
658
	 * get app's version based on it's path
659
	 *
660
	 * @param string $path
661
	 * @return string
662
	 */
663
	public static function getAppVersionByPath($path) {
664
		$infoFile = $path . '/appinfo/info.xml';
665
		$appData = self::getAppInfo($infoFile, true);
666
		return isset($appData['version']) ? $appData['version'] : '';
667
	}
668
669
670
	/**
671
	 * Read all app metadata from the info.xml file
672
	 *
673
	 * @param string $appId id of the app or the path of the info.xml file
674
	 * @param bool $path
675
	 * @param string $lang
676
	 * @return array|null
677
	 * @note all data is read from info.xml, not just pre-defined fields
678
	 */
679
	public static function getAppInfo($appId, $path = false, $lang = null) {
680
		if ($path) {
681
			$file = $appId;
682
		} else {
683
			if ($lang === null && isset(self::$appInfo[$appId])) {
684
				return self::$appInfo[$appId];
685
			}
686
			$appPath = self::getAppPath($appId);
687
			if($appPath === false) {
688
				return null;
689
			}
690
			$file = $appPath . '/appinfo/info.xml';
691
		}
692
693
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
694
		$data = $parser->parse($file);
695
696
		if (is_array($data)) {
697
			$data = OC_App::parseAppInfo($data, $lang);
698
		}
699
		if(isset($data['ocsid'])) {
700
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
701
			if($storedId !== '' && $storedId !== $data['ocsid']) {
702
				$data['ocsid'] = $storedId;
703
			}
704
		}
705
706
		if ($lang === null) {
707
			self::$appInfo[$appId] = $data;
708
		}
709
710
		return $data;
711
	}
712
713
	/**
714
	 * Returns the navigation
715
	 *
716
	 * @return array
717
	 *
718
	 * This function returns an array containing all entries added. The
719
	 * entries are sorted by the key 'order' ascending. Additional to the keys
720
	 * given for each app the following keys exist:
721
	 *   - active: boolean, signals if the user is on this navigation entry
722
	 */
723
	public static function getNavigation() {
724
		$entries = OC::$server->getNavigationManager()->getAll();
725
		return self::proceedNavigation($entries);
726
	}
727
728
	/**
729
	 * Returns the navigation inside the header bar
730
	 *
731
	 * @return array
732
	 *
733
	 * This function returns an array containing all entries added. The
734
	 * entries are sorted by the key 'order' ascending. Additional to the keys
735
	 * given for each app the following keys exist:
736
	 *   - active: boolean, signals if the user is on this navigation entry
737
	 */
738
	public static function getHeaderNavigation() {
739
		$entries = OC::$server->getNavigationManager()->getAll();
740
		return self::proceedAppNavigation($entries);
741
	}
742
743
	/**
744
	 * Returns the Settings Navigation
745
	 *
746
	 * @return string[]
747
	 *
748
	 * This function returns an array containing all settings pages added. The
749
	 * entries are sorted by the key 'order' ascending.
750
	 */
751
	public static function getSettingsNavigation() {
752
		$entries = OC::$server->getNavigationManager()->getAll('settings');
753
		return self::proceedNavigation($entries);
754
	}
755
756
	/**
757
	 * get the id of loaded app
758
	 *
759
	 * @return string
760
	 */
761
	public static function getCurrentApp() {
762
		$request = \OC::$server->getRequest();
763
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
764
		$topFolder = substr($script, 0, strpos($script, '/'));
765
		if (empty($topFolder)) {
766
			$path_info = $request->getPathInfo();
767
			if ($path_info) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path_info of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
768
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
769
			}
770
		}
771
		if ($topFolder == 'apps') {
772
			$length = strlen($topFolder);
773
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
774
		} else {
775
			return $topFolder;
776
		}
777
	}
778
779
	/**
780
	 * @param string $type
781
	 * @return array
782
	 */
783
	public static function getForms($type) {
784
		$forms = array();
785
		switch ($type) {
786
			case 'admin':
787
				$source = self::$adminForms;
788
				break;
789
			case 'personal':
790
				$source = self::$personalForms;
791
				break;
792
			default:
793
				return array();
794
		}
795
		foreach ($source as $form) {
796
			$forms[] = include $form;
797
		}
798
		return $forms;
799
	}
800
801
	/**
802
	 * register an admin form to be shown
803
	 *
804
	 * @param string $app
805
	 * @param string $page
806
	 */
807
	public static function registerAdmin($app, $page) {
808
		self::$adminForms[] = $app . '/' . $page . '.php';
809
	}
810
811
	/**
812
	 * register a personal form to be shown
813
	 * @param string $app
814
	 * @param string $page
815
	 */
816
	public static function registerPersonal($app, $page) {
817
		self::$personalForms[] = $app . '/' . $page . '.php';
818
	}
819
820
	/**
821
	 * @param array $entry
822
	 */
823
	public static function registerLogIn(array $entry) {
824
		self::$altLogin[] = $entry;
825
	}
826
827
	/**
828
	 * @return array
829
	 */
830
	public static function getAlternativeLogIns() {
831
		return self::$altLogin;
832
	}
833
834
	/**
835
	 * get a list of all apps in the apps folder
836
	 *
837
	 * @return array an array of app names (string IDs)
838
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
839
	 */
840
	public static function getAllApps() {
841
842
		$apps = array();
843
844
		foreach (OC::$APPSROOTS as $apps_dir) {
845
			if (!is_readable($apps_dir['path'])) {
846
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
847
				continue;
848
			}
849
			$dh = opendir($apps_dir['path']);
850
851
			if (is_resource($dh)) {
852
				while (($file = readdir($dh)) !== false) {
853
854
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
855
856
						$apps[] = $file;
857
					}
858
				}
859
			}
860
		}
861
862
		return $apps;
863
	}
864
865
	/**
866
	 * List all apps, this is used in apps.php
867
	 *
868
	 * @return array
869
	 */
870
	public function listAllApps() {
871
		$installedApps = OC_App::getAllApps();
872
873
		//we don't want to show configuration for these
874
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
875
		$appList = array();
876
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
877
		$urlGenerator = \OC::$server->getURLGenerator();
878
879
		foreach ($installedApps as $app) {
880
			if (array_search($app, $blacklist) === false) {
881
882
				$info = OC_App::getAppInfo($app, false, $langCode);
883
				if (!is_array($info)) {
884
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
885
					continue;
886
				}
887
888
				if (!isset($info['name'])) {
889
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
890
					continue;
891
				}
892
893
				$enabled = \OC::$server->getAppConfig()->getValue($app, 'enabled', 'no');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::getValue() has been deprecated with message: 8.0.0 use method getAppValue of \OCP\IConfig This function gets a value from the appconfig table. If the key does
not exist the default value will be returned

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
894
				$info['groups'] = null;
895
				if ($enabled === 'yes') {
896
					$active = true;
897
				} else if ($enabled === 'no') {
898
					$active = false;
899
				} else {
900
					$active = true;
901
					$info['groups'] = $enabled;
902
				}
903
904
				$info['active'] = $active;
905
906
				if (self::isShipped($app)) {
907
					$info['internal'] = true;
908
					$info['level'] = self::officialApp;
909
					$info['removable'] = false;
910
				} else {
911
					$info['internal'] = false;
912
					$info['removable'] = true;
913
				}
914
915
				$appPath = self::getAppPath($app);
916
				if($appPath !== false) {
917
					$appIcon = $appPath . '/img/' . $app . '.svg';
918
					if (file_exists($appIcon)) {
919
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
920
						$info['previewAsIcon'] = true;
921
					} else {
922
						$appIcon = $appPath . '/img/app.svg';
923
						if (file_exists($appIcon)) {
924
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
925
							$info['previewAsIcon'] = true;
926
						}
927
					}
928
				}
929
				// fix documentation
930
				if (isset($info['documentation']) && is_array($info['documentation'])) {
931
					foreach ($info['documentation'] as $key => $url) {
932
						// If it is not an absolute URL we assume it is a key
933
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
934
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
935
							$url = $urlGenerator->linkToDocs($url);
936
						}
937
938
						$info['documentation'][$key] = $url;
939
					}
940
				}
941
942
				$info['version'] = OC_App::getAppVersion($app);
943
				$appList[] = $info;
944
			}
945
		}
946
947
		return $appList;
948
	}
949
950
	/**
951
	 * Returns the internal app ID or false
952
	 * @param string $ocsID
953
	 * @return string|false
954
	 */
955
	public static function getInternalAppIdByOcs($ocsID) {
956
		if(is_numeric($ocsID)) {
957
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
958
			if(array_search($ocsID, $idArray)) {
959
				return array_search($ocsID, $idArray);
960
			}
961
		}
962
		return false;
963
	}
964
965
	public static function shouldUpgrade($app) {
966
		$versions = self::getAppVersions();
967
		$currentVersion = OC_App::getAppVersion($app);
968
		if ($currentVersion && isset($versions[$app])) {
969
			$installedVersion = $versions[$app];
970
			if (!version_compare($currentVersion, $installedVersion, '=')) {
971
				return true;
972
			}
973
		}
974
		return false;
975
	}
976
977
	/**
978
	 * Adjust the number of version parts of $version1 to match
979
	 * the number of version parts of $version2.
980
	 *
981
	 * @param string $version1 version to adjust
982
	 * @param string $version2 version to take the number of parts from
983
	 * @return string shortened $version1
984
	 */
985
	private static function adjustVersionParts($version1, $version2) {
986
		$version1 = explode('.', $version1);
987
		$version2 = explode('.', $version2);
988
		// reduce $version1 to match the number of parts in $version2
989
		while (count($version1) > count($version2)) {
990
			array_pop($version1);
991
		}
992
		// if $version1 does not have enough parts, add some
993
		while (count($version1) < count($version2)) {
994
			$version1[] = '0';
995
		}
996
		return implode('.', $version1);
997
	}
998
999
	/**
1000
	 * Check whether the current ownCloud version matches the given
1001
	 * application's version requirements.
1002
	 *
1003
	 * The comparison is made based on the number of parts that the
1004
	 * app info version has. For example for ownCloud 6.0.3 if the
1005
	 * app info version is expecting version 6.0, the comparison is
1006
	 * made on the first two parts of the ownCloud version.
1007
	 * This means that it's possible to specify "requiremin" => 6
1008
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1009
	 *
1010
	 * @param string $ocVersion ownCloud version to check against
1011
	 * @param array $appInfo app info (from xml)
1012
	 *
1013
	 * @return boolean true if compatible, otherwise false
1014
	 */
1015
	public static function isAppCompatible($ocVersion, $appInfo) {
1016
		$requireMin = '';
1017
		$requireMax = '';
1018
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
1019
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
1020 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
1021
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1022
		} else if (isset($appInfo['requiremin'])) {
1023
			$requireMin = $appInfo['requiremin'];
1024
		} else if (isset($appInfo['require'])) {
1025
			$requireMin = $appInfo['require'];
1026
		}
1027
1028
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
1029
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
1030 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
1031
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1032
		} else if (isset($appInfo['requiremax'])) {
1033
			$requireMax = $appInfo['requiremax'];
1034
		}
1035
1036
		if (is_array($ocVersion)) {
1037
			$ocVersion = implode('.', $ocVersion);
1038
		}
1039
1040 View Code Duplication
		if (!empty($requireMin)
1041
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1042
		) {
1043
1044
			return false;
1045
		}
1046
1047 View Code Duplication
		if (!empty($requireMax)
1048
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1049
		) {
1050
			return false;
1051
		}
1052
1053
		return true;
1054
	}
1055
1056
	/**
1057
	 * get the installed version of all apps
1058
	 */
1059
	public static function getAppVersions() {
1060
		static $versions;
1061
1062
		if(!$versions) {
1063
			$appConfig = \OC::$server->getAppConfig();
1064
			$versions = $appConfig->getValues(false, 'installed_version');
1065
		}
1066
		return $versions;
1067
	}
1068
1069
	/**
1070
	 * @param string $app
1071
	 * @param \OCP\IConfig $config
1072
	 * @param \OCP\IL10N $l
1073
	 * @return bool
1074
	 *
1075
	 * @throws Exception if app is not compatible with this version of ownCloud
1076
	 * @throws Exception if no app-name was specified
1077
	 */
1078
	public function installApp($app,
1079
							   \OCP\IConfig $config,
1080
							   \OCP\IL10N $l) {
1081
		if ($app !== false) {
1082
			// check if the app is compatible with this version of ownCloud
1083
			$info = self::getAppInfo($app);
1084
			if(!is_array($info)) {
1085
				throw new \Exception(
1086
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1087
						[$info['name']]
1088
					)
1089
				);
1090
			}
1091
1092
			$version = \OCP\Util::getVersion();
1093
			if (!self::isAppCompatible($version, $info)) {
0 ignored issues
show
Documentation introduced by
$version is of type array, but the function expects a string.

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...
1094
				throw new \Exception(
1095
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1096
						array($info['name'])
1097
					)
1098
				);
1099
			}
1100
1101
			// check for required dependencies
1102
			self::checkAppDependencies($config, $l, $info);
1103
1104
			$config->setAppValue($app, 'enabled', 'yes');
1105
			if (isset($appData['id'])) {
0 ignored issues
show
Bug introduced by
The variable $appData seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1106
				$config->setAppValue($app, 'ocsid', $appData['id']);
1107
			}
1108
1109 View Code Duplication
			if(isset($info['settings']) && is_array($info['settings'])) {
1110
				$appPath = self::getAppPath($app);
1111
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1110 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1112
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1113
			}
1114
1115
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1116
		} else {
1117
			if(empty($appName) ) {
0 ignored issues
show
Bug introduced by
The variable $appName seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1118
				throw new \Exception($l->t("No app name specified"));
1119
			} else {
1120
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1121
			}
1122
		}
1123
1124
		return $app;
1125
	}
1126
1127
	/**
1128
	 * update the database for the app and call the update script
1129
	 *
1130
	 * @param string $appId
1131
	 * @return bool
1132
	 */
1133
	public static function updateApp($appId) {
1134
		$appPath = self::getAppPath($appId);
1135
		if($appPath === false) {
1136
			return false;
1137
		}
1138
		$appData = self::getAppInfo($appId);
1139
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1140
		if (file_exists($appPath . '/appinfo/database.xml')) {
1141
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1142
		}
1143
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1144
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1145
		unset(self::$appVersion[$appId]);
1146
		// run upgrade code
1147
		if (file_exists($appPath . '/appinfo/update.php')) {
1148
			self::loadApp($appId);
1149
			include $appPath . '/appinfo/update.php';
1150
		}
1151
		self::setupBackgroundJobs($appData['background-jobs']);
1152 View Code Duplication
		if(isset($appData['settings']) && is_array($appData['settings'])) {
1153
			$appPath = self::getAppPath($appId);
1154
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1153 can also be of type false; however, OC_App::registerAutoloading() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1155
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1156
		}
1157
1158
		//set remote/public handlers
1159
		if (array_key_exists('ocsid', $appData)) {
1160
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1161 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1162
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1163
		}
1164
		foreach ($appData['remote'] as $name => $path) {
1165
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1166
		}
1167 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
1168
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1169
		}
1170
1171
		self::setAppTypes($appId);
1172
1173
		$version = \OC_App::getAppVersion($appId);
1174
		\OC::$server->getAppConfig()->setValue($appId, 'installed_version', $version);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1175
1176
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1177
			ManagerEvent::EVENT_APP_UPDATE, $appId
1178
		));
1179
1180
		return true;
1181
	}
1182
1183
	/**
1184
	 * @param string $appId
1185
	 * @param string[] $steps
1186
	 * @throws \OC\NeedsUpdateException
1187
	 */
1188
	public static function executeRepairSteps($appId, array $steps) {
1189
		if (empty($steps)) {
1190
			return;
1191
		}
1192
		// load the app
1193
		self::loadApp($appId);
1194
1195
		$dispatcher = OC::$server->getEventDispatcher();
1196
1197
		// load the steps
1198
		$r = new Repair([], $dispatcher);
1199
		foreach ($steps as $step) {
1200
			try {
1201
				$r->addStep($step);
1202
			} catch (Exception $ex) {
1203
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1204
				\OC::$server->getLogger()->logException($ex);
1205
			}
1206
		}
1207
		// run the steps
1208
		$r->run();
1209
	}
1210
1211
	public static function setupBackgroundJobs(array $jobs) {
1212
		$queue = \OC::$server->getJobList();
1213
		foreach ($jobs as $job) {
1214
			$queue->add($job);
1215
		}
1216
	}
1217
1218
	/**
1219
	 * @param string $appId
1220
	 * @param string[] $steps
1221
	 */
1222
	private static function setupLiveMigrations($appId, array $steps) {
1223
		$queue = \OC::$server->getJobList();
1224
		foreach ($steps as $step) {
1225
			$queue->add('OC\Migration\BackgroundRepair', [
1226
				'app' => $appId,
1227
				'step' => $step]);
1228
		}
1229
	}
1230
1231
	/**
1232
	 * @param string $appId
1233
	 * @return \OC\Files\View|false
1234
	 */
1235
	public static function getStorage($appId) {
1236
		if (OC_App::isEnabled($appId)) { //sanity check
1237
			if (\OC::$server->getUserSession()->isLoggedIn()) {
1238
				$view = new \OC\Files\View('/' . OC_User::getUser());
1239
				if (!$view->file_exists($appId)) {
1240
					$view->mkdir($appId);
1241
				}
1242
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1243
			} else {
1244
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1245
				return false;
1246
			}
1247
		} else {
1248
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1249
			return false;
1250
		}
1251
	}
1252
1253
	protected static function findBestL10NOption($options, $lang) {
1254
		$fallback = $similarLangFallback = $englishFallback = false;
1255
1256
		$lang = strtolower($lang);
1257
		$similarLang = $lang;
1258
		if (strpos($similarLang, '_')) {
1259
			// For "de_DE" we want to find "de" and the other way around
1260
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1261
		}
1262
1263
		foreach ($options as $option) {
1264
			if (is_array($option)) {
1265
				if ($fallback === false) {
1266
					$fallback = $option['@value'];
1267
				}
1268
1269
				if (!isset($option['@attributes']['lang'])) {
1270
					continue;
1271
				}
1272
1273
				$attributeLang = strtolower($option['@attributes']['lang']);
1274
				if ($attributeLang === $lang) {
1275
					return $option['@value'];
1276
				}
1277
1278
				if ($attributeLang === $similarLang) {
1279
					$similarLangFallback = $option['@value'];
1280
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1281
					if ($similarLangFallback === false) {
1282
						$similarLangFallback =  $option['@value'];
1283
					}
1284
				}
1285
			} else {
1286
				$englishFallback = $option;
1287
			}
1288
		}
1289
1290
		if ($similarLangFallback !== false) {
1291
			return $similarLangFallback;
1292
		} else if ($englishFallback !== false) {
1293
			return $englishFallback;
1294
		}
1295
		return (string) $fallback;
1296
	}
1297
1298
	/**
1299
	 * parses the app data array and enhanced the 'description' value
1300
	 *
1301
	 * @param array $data the app data
1302
	 * @param string $lang
1303
	 * @return array improved app data
1304
	 */
1305
	public static function parseAppInfo(array $data, $lang = null) {
1306
1307 View Code Duplication
		if ($lang && isset($data['name']) && is_array($data['name'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; 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...
1308
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1309
		}
1310 View Code Duplication
		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; 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...
1311
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1312
		}
1313
		if ($lang && isset($data['description']) && is_array($data['description'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type string|null is loosely compared to true; 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...
1314
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1315
		} else if (isset($data['description']) && is_string($data['description'])) {
1316
			$data['description'] = trim($data['description']);
1317
		} else  {
1318
			$data['description'] = '';
1319
		}
1320
1321
		return $data;
1322
	}
1323
1324
	/**
1325
	 * @param \OCP\IConfig $config
1326
	 * @param \OCP\IL10N $l
1327
	 * @param array $info
1328
	 * @throws \Exception
1329
	 */
1330
	protected static function checkAppDependencies($config, $l, $info) {
1331
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1332
		$missing = $dependencyAnalyzer->analyze($info);
1333
		if (!empty($missing)) {
1334
			$missingMsg = join(PHP_EOL, $missing);
1335
			throw new \Exception(
1336
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1337
					[$info['name'], $missingMsg]
1338
				)
1339
			);
1340
		}
1341
	}
1342
}
1343