Completed
Push — master ( 61c68d...35b8f0 )
by Joas
08:47 queued 30s
created

OC_App::parseAppInfo()   B

Complexity

Conditions 12
Paths 12

Size

Total Lines 18
Code Lines 12

Duplication

Lines 6
Ratio 33.33 %

Importance

Changes 0
Metric Value
cc 12
eloc 12
nc 12
nop 2
dl 6
loc 18
rs 7.4259
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * @param bool $checkUpgrade whether an upgrade check should be done
138
	 * @throws \OC\NeedsUpdateException
139
	 */
140
	public static function loadApp($app, $checkUpgrade = true) {
141
		self::$loadedApps[] = $app;
142
		$appPath = self::getAppPath($app);
143
		if($appPath === false) {
144
			return;
145
		}
146
147
		// in case someone calls loadApp() directly
148
		self::registerAutoloading($app, $appPath);
149
150
		if (is_file($appPath . '/appinfo/app.php')) {
151
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
152
			if ($checkUpgrade and self::shouldUpgrade($app)) {
153
				throw new \OC\NeedsUpdateException();
154
			}
155
			self::requireAppFile($app);
156
			if (self::isType($app, array('authentication'))) {
157
				// since authentication apps affect the "is app enabled for group" check,
158
				// the enabled apps cache needs to be cleared to make sure that the
159
				// next time getEnableApps() is called it will also include apps that were
160
				// enabled for groups
161
				self::$enabledAppsCache = array();
162
			}
163
			\OC::$server->getEventLogger()->end('load_app_' . $app);
164
		}
165
166
		$info = self::getAppInfo($app);
167 View Code Duplication
		if (!empty($info['activity']['filters'])) {
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...
168
			foreach ($info['activity']['filters'] as $filter) {
169
				\OC::$server->getActivityManager()->registerFilter($filter);
170
			}
171
		}
172 View Code Duplication
		if (!empty($info['activity']['settings'])) {
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...
173
			foreach ($info['activity']['settings'] as $setting) {
174
				\OC::$server->getActivityManager()->registerSetting($setting);
175
			}
176
		}
177 View Code Duplication
		if (!empty($info['activity']['providers'])) {
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...
178
			foreach ($info['activity']['providers'] as $provider) {
179
				\OC::$server->getActivityManager()->registerProvider($provider);
180
			}
181
		}
182
	}
183
184
	/**
185
	 * @internal
186
	 * @param string $app
187
	 * @param string $path
188
	 */
189
	public static function registerAutoloading($app, $path) {
190
		$key = $app . '-' . $path;
191
		if(isset(self::$alreadyRegistered[$key])) {
192
			return;
193
		}
194
		self::$alreadyRegistered[$key] = true;
195
		// Register on PSR-4 composer autoloader
196
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
197
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
198
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
199
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
200
		}
201
202
		// Register on legacy autoloader
203
		\OC::$loader->addValidRoot($path);
204
	}
205
206
	/**
207
	 * Load app.php from the given app
208
	 *
209
	 * @param string $app app name
210
	 */
211
	private static function requireAppFile($app) {
212
		try {
213
			// encapsulated here to avoid variable scope conflicts
214
			require_once $app . '/appinfo/app.php';
215
		} 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...
216
			\OC::$server->getLogger()->logException($ex);
217
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
218
			if (!in_array($app, $blacklist)) {
219
				self::disable($app);
220
			}
221
		}
222
	}
223
224
	/**
225
	 * check if an app is of a specific type
226
	 *
227
	 * @param string $app
228
	 * @param string|array $types
229
	 * @return bool
230
	 */
231
	public static function isType($app, $types) {
232
		if (is_string($types)) {
233
			$types = array($types);
234
		}
235
		$appTypes = self::getAppTypes($app);
236
		foreach ($types as $type) {
237
			if (array_search($type, $appTypes) !== false) {
238
				return true;
239
			}
240
		}
241
		return false;
242
	}
243
244
	/**
245
	 * get the types of an app
246
	 *
247
	 * @param string $app
248
	 * @return array
249
	 */
250
	private static function getAppTypes($app) {
251
		//load the cache
252
		if (count(self::$appTypes) == 0) {
253
			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...
254
		}
255
256
		if (isset(self::$appTypes[$app])) {
257
			return explode(',', self::$appTypes[$app]);
258
		} else {
259
			return array();
260
		}
261
	}
262
263
	/**
264
	 * read app types from info.xml and cache them in the database
265
	 */
266
	public static function setAppTypes($app) {
267
		$appData = self::getAppInfo($app);
268
		if(!is_array($appData)) {
269
			return;
270
		}
271
272
		if (isset($appData['types'])) {
273
			$appTypes = implode(',', $appData['types']);
274
		} else {
275
			$appTypes = '';
276
			$appData['types'] = [];
277
		}
278
279
		\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...
280
281
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
282
			$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...
283
			if ($enabled !== 'yes' && $enabled !== 'no') {
284
				\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...
285
			}
286
		}
287
	}
288
289
	/**
290
	 * check if app is shipped
291
	 *
292
	 * @param string $appId the id of the app to check
293
	 * @return bool
294
	 *
295
	 * Check if an app that is installed is a shipped app or installed from the appstore.
296
	 */
297
	public static function isShipped($appId) {
298
		return \OC::$server->getAppManager()->isShipped($appId);
299
	}
300
301
	/**
302
	 * get all enabled apps
303
	 */
304
	protected static $enabledAppsCache = array();
305
306
	/**
307
	 * Returns apps enabled for the current user.
308
	 *
309
	 * @param bool $forceRefresh whether to refresh the cache
310
	 * @param bool $all whether to return apps for all users, not only the
311
	 * currently logged in one
312
	 * @return string[]
313
	 */
314
	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...
315
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
316
			return array();
317
		}
318
		// in incognito mode or when logged out, $user will be false,
319
		// which is also the case during an upgrade
320
		$appManager = \OC::$server->getAppManager();
321
		if ($all) {
322
			$user = null;
323
		} else {
324
			$user = \OC::$server->getUserSession()->getUser();
325
		}
326
327
		if (is_null($user)) {
328
			$apps = $appManager->getInstalledApps();
329
		} else {
330
			$apps = $appManager->getEnabledAppsForUser($user);
331
		}
332
		$apps = array_filter($apps, function ($app) {
333
			return $app !== 'files';//we add this manually
334
		});
335
		sort($apps);
336
		array_unshift($apps, 'files');
337
		return $apps;
338
	}
339
340
	/**
341
	 * checks whether or not an app is enabled
342
	 *
343
	 * @param string $app app
344
	 * @return bool
345
	 *
346
	 * This function checks whether or not an app is enabled.
347
	 */
348
	public static function isEnabled($app) {
349
		return \OC::$server->getAppManager()->isEnabledForUser($app);
350
	}
351
352
	/**
353
	 * enables an app
354
	 *
355
	 * @param string $appId
356
	 * @param array $groups (optional) when set, only these groups will have access to the app
357
	 * @throws \Exception
358
	 * @return void
359
	 *
360
	 * This function set an app as enabled in appconfig.
361
	 */
362
	public function enable($appId,
363
						   $groups = null) {
364
		self::$enabledAppsCache = []; // flush
365
		$l = \OC::$server->getL10N('core');
366
		$config = \OC::$server->getConfig();
367
368
		// Check if app is already downloaded
369
		$installer = new Installer(
370
			\OC::$server->getAppFetcher(),
371
			\OC::$server->getHTTPClientService(),
372
			\OC::$server->getTempManager(),
373
			\OC::$server->getLogger()
374
		);
375
		$isDownloaded = $installer->isDownloaded($appId);
376
377
		if(!$isDownloaded) {
378
			$installer->downloadApp($appId);
379
		}
380
381
		if (!Installer::isInstalled($appId)) {
382
			$appId = self::installApp(
383
				$appId,
384
				$config,
385
				$l
386
			);
387
			$appPath = self::getAppPath($appId);
388
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 387 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...
389
			$installer->installApp($appId);
390
		} else {
391
			// check for required dependencies
392
			$info = self::getAppInfo($appId);
393
			self::checkAppDependencies($config, $l, $info);
0 ignored issues
show
Bug introduced by
It seems like $info defined by self::getAppInfo($appId) on line 392 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...
394
			$appPath = self::getAppPath($appId);
395
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 394 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...
396
			$installer->installApp($appId);
397
		}
398
399
		$appManager = \OC::$server->getAppManager();
400
		if (!is_null($groups)) {
401
			$groupManager = \OC::$server->getGroupManager();
402
			$groupsList = [];
403
			foreach ($groups as $group) {
404
				$groupItem = $groupManager->get($group);
405
				if ($groupItem instanceof \OCP\IGroup) {
406
					$groupsList[] = $groupManager->get($group);
407
				}
408
			}
409
			$appManager->enableAppForGroups($appId, $groupsList);
410
		} else {
411
			$appManager->enableApp($appId);
412
		}
413
414
		$info = self::getAppInfo($appId);
415 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
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...
416
			$appPath = self::getAppPath($appId);
417
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 416 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...
418
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
419
		}
420
	}
421
422
	/**
423
	 * @param string $app
424
	 * @return bool
425
	 */
426
	public static function removeApp($app) {
427
		if (self::isShipped($app)) {
428
			return false;
429
		}
430
431
		$installer = new Installer(
432
			\OC::$server->getAppFetcher(),
433
			\OC::$server->getHTTPClientService(),
434
			\OC::$server->getTempManager(),
435
			\OC::$server->getLogger()
436
		);
437
		return $installer->removeApp($app);
438
	}
439
440
	/**
441
	 * This function set an app as disabled in appconfig.
442
	 *
443
	 * @param string $app app
444
	 * @throws Exception
445
	 */
446
	public static function disable($app) {
447
		// flush
448
		self::$enabledAppsCache = array();
449
450
		// run uninstall steps
451
		$appData = OC_App::getAppInfo($app);
452
		if (!is_null($appData)) {
453
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
454
		}
455
456
		// emit disable hook - needed anymore ?
457
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
458
459
		// finally disable it
460
		$appManager = \OC::$server->getAppManager();
461
		$appManager->disableApp($app);
462
	}
463
464
	/**
465
	 * Returns the Settings Navigation
466
	 *
467
	 * @return string[]
468
	 *
469
	 * This function returns an array containing all settings pages added. The
470
	 * entries are sorted by the key 'order' ascending.
471
	 */
472
	public static function getSettingsNavigation() {
473
		$l = \OC::$server->getL10N('lib');
474
		$urlGenerator = \OC::$server->getURLGenerator();
475
476
		$settings = array();
477
		// by default, settings only contain the help menu
478
		if (\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true)) {
479
			$settings = array(
480
				array(
481
					"id" => "help",
482
					"order" => 4,
483
					"href" => $urlGenerator->linkToRoute('settings_help'),
484
					"name" => $l->t("Help"),
485
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
486
				)
487
			);
488
		}
489
490
		// if the user is logged-in
491
		if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

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...
492
			// personal menu
493
			$settings[] = array(
494
				"id" => "personal",
495
				"order" => 1,
496
				"href" => $urlGenerator->linkToRoute('settings_personal'),
497
				"name" => $l->t("Personal"),
498
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
499
			);
500
501
			//SubAdmins are also allowed to access user management
502
			$userObject = \OC::$server->getUserSession()->getUser();
503
			$isSubAdmin = false;
504
			if($userObject !== null) {
505
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
506
			}
507
			if ($isSubAdmin) {
508
				// admin users menu
509
				$settings[] = array(
510
					"id" => "core_users",
511
					"order" => 3,
512
					"href" => $urlGenerator->linkToRoute('settings_users'),
513
					"name" => $l->t("Users"),
514
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
515
				);
516
			}
517
518
			// if the user is an admin
519
			if (OC_User::isAdminUser(OC_User::getUser())) {
520
				// admin settings
521
				$settings[] = array(
522
					"id" => "admin",
523
					"order" => 2,
524
					"href" => $urlGenerator->linkToRoute('settings.AdminSettings.index'),
525
					"name" => $l->t("Admin"),
526
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
527
				);
528
			}
529
		}
530
531
		$navigation = self::proceedNavigation($settings);
532
		return $navigation;
533
	}
534
535
	// This is private as well. It simply works, so don't ask for more details
536
	private static function proceedNavigation($list) {
537
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
538
		foreach ($list as &$navEntry) {
539
			if ($navEntry['id'] == $activeApp) {
540
				$navEntry['active'] = true;
541
			} else {
542
				$navEntry['active'] = false;
543
			}
544
		}
545
		unset($navEntry);
546
547
		usort($list, function($a, $b) {
548
			if (isset($a['order']) && isset($b['order'])) {
549
				return ($a['order'] < $b['order']) ? -1 : 1;
550
			} else if (isset($a['order']) || isset($b['order'])) {
551
				return isset($a['order']) ? -1 : 1;
552
			} else {
553
				return ($a['name'] < $b['name']) ? -1 : 1;
554
			}
555
		});
556
557
		return $list;
558
	}
559
560
	/**
561
	 * Get the path where to install apps
562
	 *
563
	 * @return string|false
564
	 */
565
	public static function getInstallPath() {
566
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
567
			return false;
568
		}
569
570
		foreach (OC::$APPSROOTS as $dir) {
571
			if (isset($dir['writable']) && $dir['writable'] === true) {
572
				return $dir['path'];
573
			}
574
		}
575
576
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
577
		return null;
578
	}
579
580
581
	/**
582
	 * search for an app in all app-directories
583
	 *
584
	 * @param string $appId
585
	 * @return false|string
586
	 */
587
	public static function findAppInDirectories($appId) {
588
		$sanitizedAppId = self::cleanAppId($appId);
589
		if($sanitizedAppId !== $appId) {
590
			return false;
591
		}
592
		static $app_dir = array();
593
594
		if (isset($app_dir[$appId])) {
595
			return $app_dir[$appId];
596
		}
597
598
		$possibleApps = array();
599
		foreach (OC::$APPSROOTS as $dir) {
600
			if (file_exists($dir['path'] . '/' . $appId)) {
601
				$possibleApps[] = $dir;
602
			}
603
		}
604
605
		if (empty($possibleApps)) {
606
			return false;
607
		} elseif (count($possibleApps) === 1) {
608
			$dir = array_shift($possibleApps);
609
			$app_dir[$appId] = $dir;
610
			return $dir;
611
		} else {
612
			$versionToLoad = array();
613
			foreach ($possibleApps as $possibleApp) {
614
				$version = self::getAppVersionByPath($possibleApp['path']);
615
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
616
					$versionToLoad = array(
617
						'dir' => $possibleApp,
618
						'version' => $version,
619
					);
620
				}
621
			}
622
			$app_dir[$appId] = $versionToLoad['dir'];
623
			return $versionToLoad['dir'];
624
			//TODO - write test
625
		}
626
	}
627
628
	/**
629
	 * Get the directory for the given app.
630
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
631
	 *
632
	 * @param string $appId
633
	 * @return string|false
634
	 */
635
	public static function getAppPath($appId) {
636
		if ($appId === null || trim($appId) === '') {
637
			return false;
638
		}
639
640
		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...
641
			return $dir['path'] . '/' . $appId;
642
		}
643
		return false;
644
	}
645
646
	/**
647
	 * Get the path for the given app on the access
648
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
649
	 *
650
	 * @param string $appId
651
	 * @return string|false
652
	 */
653
	public static function getAppWebPath($appId) {
654
		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...
655
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
656
		}
657
		return false;
658
	}
659
660
	/**
661
	 * get the last version of the app from appinfo/info.xml
662
	 *
663
	 * @param string $appId
664
	 * @param bool $useCache
665
	 * @return string
666
	 */
667
	public static function getAppVersion($appId, $useCache = true) {
668
		if($useCache && isset(self::$appVersion[$appId])) {
669
			return self::$appVersion[$appId];
670
		}
671
672
		$file = self::getAppPath($appId);
673
		self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
674
		return self::$appVersion[$appId];
675
	}
676
677
	/**
678
	 * get app's version based on it's path
679
	 *
680
	 * @param string $path
681
	 * @return string
682
	 */
683
	public static function getAppVersionByPath($path) {
684
		$infoFile = $path . '/appinfo/info.xml';
685
		$appData = self::getAppInfo($infoFile, true);
686
		return isset($appData['version']) ? $appData['version'] : '';
687
	}
688
689
690
	/**
691
	 * Read all app metadata from the info.xml file
692
	 *
693
	 * @param string $appId id of the app or the path of the info.xml file
694
	 * @param bool $path
695
	 * @param string $lang
696
	 * @return array|null
697
	 * @note all data is read from info.xml, not just pre-defined fields
698
	 */
699
	public static function getAppInfo($appId, $path = false, $lang = null) {
700
		if ($path) {
701
			$file = $appId;
702
		} else {
703
			if ($lang === null && isset(self::$appInfo[$appId])) {
704
				return self::$appInfo[$appId];
705
			}
706
			$appPath = self::getAppPath($appId);
707
			if($appPath === false) {
708
				return null;
709
			}
710
			$file = $appPath . '/appinfo/info.xml';
711
		}
712
713
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
714
		$data = $parser->parse($file);
715
716
		if (is_array($data)) {
717
			$data = OC_App::parseAppInfo($data, $lang);
718
		}
719
		if(isset($data['ocsid'])) {
720
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
721
			if($storedId !== '' && $storedId !== $data['ocsid']) {
722
				$data['ocsid'] = $storedId;
723
			}
724
		}
725
726
		if ($lang === null) {
727
			self::$appInfo[$appId] = $data;
728
		}
729
730
		return $data;
731
	}
732
733
	/**
734
	 * Returns the navigation
735
	 *
736
	 * @return array
737
	 *
738
	 * This function returns an array containing all entries added. The
739
	 * entries are sorted by the key 'order' ascending. Additional to the keys
740
	 * given for each app the following keys exist:
741
	 *   - active: boolean, signals if the user is on this navigation entry
742
	 */
743
	public static function getNavigation() {
744
		$entries = OC::$server->getNavigationManager()->getAll();
745
		$navigation = self::proceedNavigation($entries);
746
		return $navigation;
747
	}
748
749
	/**
750
	 * get the id of loaded app
751
	 *
752
	 * @return string
753
	 */
754
	public static function getCurrentApp() {
755
		$request = \OC::$server->getRequest();
756
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
757
		$topFolder = substr($script, 0, strpos($script, '/'));
758
		if (empty($topFolder)) {
759
			$path_info = $request->getPathInfo();
760
			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...
761
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
762
			}
763
		}
764
		if ($topFolder == 'apps') {
765
			$length = strlen($topFolder);
766
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
767
		} else {
768
			return $topFolder;
769
		}
770
	}
771
772
	/**
773
	 * @param string $type
774
	 * @return array
775
	 */
776
	public static function getForms($type) {
777
		$forms = array();
778
		switch ($type) {
779
			case 'admin':
780
				$source = self::$adminForms;
781
				break;
782
			case 'personal':
783
				$source = self::$personalForms;
784
				break;
785
			default:
786
				return array();
787
		}
788
		foreach ($source as $form) {
789
			$forms[] = include $form;
790
		}
791
		return $forms;
792
	}
793
794
	/**
795
	 * register an admin form to be shown
796
	 *
797
	 * @param string $app
798
	 * @param string $page
799
	 */
800
	public static function registerAdmin($app, $page) {
801
		self::$adminForms[] = $app . '/' . $page . '.php';
802
	}
803
804
	/**
805
	 * register a personal form to be shown
806
	 * @param string $app
807
	 * @param string $page
808
	 */
809
	public static function registerPersonal($app, $page) {
810
		self::$personalForms[] = $app . '/' . $page . '.php';
811
	}
812
813
	/**
814
	 * @param array $entry
815
	 */
816
	public static function registerLogIn(array $entry) {
817
		self::$altLogin[] = $entry;
818
	}
819
820
	/**
821
	 * @return array
822
	 */
823
	public static function getAlternativeLogIns() {
824
		return self::$altLogin;
825
	}
826
827
	/**
828
	 * get a list of all apps in the apps folder
829
	 *
830
	 * @return array an array of app names (string IDs)
831
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
832
	 */
833
	public static function getAllApps() {
834
835
		$apps = array();
836
837
		foreach (OC::$APPSROOTS as $apps_dir) {
838
			if (!is_readable($apps_dir['path'])) {
839
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
840
				continue;
841
			}
842
			$dh = opendir($apps_dir['path']);
843
844
			if (is_resource($dh)) {
845
				while (($file = readdir($dh)) !== false) {
846
847
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
848
849
						$apps[] = $file;
850
					}
851
				}
852
			}
853
		}
854
855
		return $apps;
856
	}
857
858
	/**
859
	 * List all apps, this is used in apps.php
860
	 *
861
	 * @return array
862
	 */
863
	public function listAllApps() {
864
		$installedApps = OC_App::getAllApps();
865
866
		//we don't want to show configuration for these
867
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
868
		$appList = array();
869
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
870
		$urlGenerator = \OC::$server->getURLGenerator();
871
872
		foreach ($installedApps as $app) {
873
			if (array_search($app, $blacklist) === false) {
874
875
				$info = OC_App::getAppInfo($app, false, $langCode);
876
				if (!is_array($info)) {
877
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
878
					continue;
879
				}
880
881
				if (!isset($info['name'])) {
882
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
883
					continue;
884
				}
885
886
				$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...
887
				$info['groups'] = null;
888
				if ($enabled === 'yes') {
889
					$active = true;
890
				} else if ($enabled === 'no') {
891
					$active = false;
892
				} else {
893
					$active = true;
894
					$info['groups'] = $enabled;
895
				}
896
897
				$info['active'] = $active;
898
899
				if (self::isShipped($app)) {
900
					$info['internal'] = true;
901
					$info['level'] = self::officialApp;
902
					$info['removable'] = false;
903
				} else {
904
					$info['internal'] = false;
905
					$info['removable'] = true;
906
				}
907
908
				$appPath = self::getAppPath($app);
909
				if($appPath !== false) {
910
					$appIcon = $appPath . '/img/' . $app . '.svg';
911
					if (file_exists($appIcon)) {
912
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
913
						$info['previewAsIcon'] = true;
914
					} else {
915
						$appIcon = $appPath . '/img/app.svg';
916
						if (file_exists($appIcon)) {
917
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
918
							$info['previewAsIcon'] = true;
919
						}
920
					}
921
				}
922
				// fix documentation
923
				if (isset($info['documentation']) && is_array($info['documentation'])) {
924
					foreach ($info['documentation'] as $key => $url) {
925
						// If it is not an absolute URL we assume it is a key
926
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
927
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
928
							$url = $urlGenerator->linkToDocs($url);
929
						}
930
931
						$info['documentation'][$key] = $url;
932
					}
933
				}
934
935
				$info['version'] = OC_App::getAppVersion($app);
936
				$appList[] = $info;
937
			}
938
		}
939
940
		return $appList;
941
	}
942
943
	/**
944
	 * Returns the internal app ID or false
945
	 * @param string $ocsID
946
	 * @return string|false
947
	 */
948
	public static function getInternalAppIdByOcs($ocsID) {
949
		if(is_numeric($ocsID)) {
950
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
951
			if(array_search($ocsID, $idArray)) {
952
				return array_search($ocsID, $idArray);
953
			}
954
		}
955
		return false;
956
	}
957
958
	public static function shouldUpgrade($app) {
959
		$versions = self::getAppVersions();
960
		$currentVersion = OC_App::getAppVersion($app);
961
		if ($currentVersion && isset($versions[$app])) {
962
			$installedVersion = $versions[$app];
963
			if (!version_compare($currentVersion, $installedVersion, '=')) {
964
				return true;
965
			}
966
		}
967
		return false;
968
	}
969
970
	/**
971
	 * Adjust the number of version parts of $version1 to match
972
	 * the number of version parts of $version2.
973
	 *
974
	 * @param string $version1 version to adjust
975
	 * @param string $version2 version to take the number of parts from
976
	 * @return string shortened $version1
977
	 */
978
	private static function adjustVersionParts($version1, $version2) {
979
		$version1 = explode('.', $version1);
980
		$version2 = explode('.', $version2);
981
		// reduce $version1 to match the number of parts in $version2
982
		while (count($version1) > count($version2)) {
983
			array_pop($version1);
984
		}
985
		// if $version1 does not have enough parts, add some
986
		while (count($version1) < count($version2)) {
987
			$version1[] = '0';
988
		}
989
		return implode('.', $version1);
990
	}
991
992
	/**
993
	 * Check whether the current ownCloud version matches the given
994
	 * application's version requirements.
995
	 *
996
	 * The comparison is made based on the number of parts that the
997
	 * app info version has. For example for ownCloud 6.0.3 if the
998
	 * app info version is expecting version 6.0, the comparison is
999
	 * made on the first two parts of the ownCloud version.
1000
	 * This means that it's possible to specify "requiremin" => 6
1001
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
1002
	 *
1003
	 * @param string $ocVersion ownCloud version to check against
1004
	 * @param array $appInfo app info (from xml)
1005
	 *
1006
	 * @return boolean true if compatible, otherwise false
1007
	 */
1008
	public static function isAppCompatible($ocVersion, $appInfo) {
1009
		$requireMin = '';
1010
		$requireMax = '';
1011
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
1012
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
1013 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
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...
1014
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1015
		} else if (isset($appInfo['requiremin'])) {
1016
			$requireMin = $appInfo['requiremin'];
1017
		} else if (isset($appInfo['require'])) {
1018
			$requireMin = $appInfo['require'];
1019
		}
1020
1021
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
1022
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
1023 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
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...
1024
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1025
		} else if (isset($appInfo['requiremax'])) {
1026
			$requireMax = $appInfo['requiremax'];
1027
		}
1028
1029
		if (is_array($ocVersion)) {
1030
			$ocVersion = implode('.', $ocVersion);
1031
		}
1032
1033 View Code Duplication
		if (!empty($requireMin)
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...
1034
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1035
		) {
1036
1037
			return false;
1038
		}
1039
1040 View Code Duplication
		if (!empty($requireMax)
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...
1041
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1042
		) {
1043
			return false;
1044
		}
1045
1046
		return true;
1047
	}
1048
1049
	/**
1050
	 * get the installed version of all apps
1051
	 */
1052
	public static function getAppVersions() {
1053
		static $versions;
1054
1055
		if(!$versions) {
1056
			$appConfig = \OC::$server->getAppConfig();
1057
			$versions = $appConfig->getValues(false, 'installed_version');
1058
		}
1059
		return $versions;
1060
	}
1061
1062
	/**
1063
	 * @param string $app
1064
	 * @param \OCP\IConfig $config
1065
	 * @param \OCP\IL10N $l
1066
	 * @return bool
1067
	 *
1068
	 * @throws Exception if app is not compatible with this version of ownCloud
1069
	 * @throws Exception if no app-name was specified
1070
	 */
1071
	public function installApp($app,
1072
							   \OCP\IConfig $config,
1073
							   \OCP\IL10N $l) {
1074
		if ($app !== false) {
1075
			// check if the app is compatible with this version of ownCloud
1076
			$info = self::getAppInfo($app);
1077
			if(!is_array($info)) {
1078
				throw new \Exception(
1079
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1080
						[$info['name']]
1081
					)
1082
				);
1083
			}
1084
1085
			$version = \OCP\Util::getVersion();
1086
			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...
1087
				throw new \Exception(
1088
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1089
						array($info['name'])
1090
					)
1091
				);
1092
			}
1093
1094
			// check for required dependencies
1095
			self::checkAppDependencies($config, $l, $info);
1096
1097
			$config->setAppValue($app, 'enabled', 'yes');
1098
			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...
1099
				$config->setAppValue($app, 'ocsid', $appData['id']);
1100
			}
1101
1102 View Code Duplication
			if(isset($info['settings']) && is_array($info['settings'])) {
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...
1103
				$appPath = self::getAppPath($app);
1104
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1103 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...
1105
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1106
			}
1107
1108
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1109
		} else {
1110
			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...
1111
				throw new \Exception($l->t("No app name specified"));
1112
			} else {
1113
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1114
			}
1115
		}
1116
1117
		return $app;
1118
	}
1119
1120
	/**
1121
	 * update the database for the app and call the update script
1122
	 *
1123
	 * @param string $appId
1124
	 * @return bool
1125
	 */
1126
	public static function updateApp($appId) {
1127
		$appPath = self::getAppPath($appId);
1128
		if($appPath === false) {
1129
			return false;
1130
		}
1131
		$appData = self::getAppInfo($appId);
1132
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1133
		if (file_exists($appPath . '/appinfo/database.xml')) {
1134
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1135
		}
1136
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1137
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1138
		unset(self::$appVersion[$appId]);
1139
		// run upgrade code
1140
		if (file_exists($appPath . '/appinfo/update.php')) {
1141
			self::loadApp($appId, false);
1142
			include $appPath . '/appinfo/update.php';
1143
		}
1144
		self::setupBackgroundJobs($appData['background-jobs']);
1145 View Code Duplication
		if(isset($appData['settings']) && is_array($appData['settings'])) {
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...
1146
			$appPath = self::getAppPath($appId);
1147
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1146 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...
1148
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1149
		}
1150
1151
		//set remote/public handlers
1152
		if (array_key_exists('ocsid', $appData)) {
1153
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1154 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
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...
1155
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1156
		}
1157
		foreach ($appData['remote'] as $name => $path) {
1158
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1159
		}
1160 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
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...
1161
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1162
		}
1163
1164
		self::setAppTypes($appId);
1165
1166
		$version = \OC_App::getAppVersion($appId);
1167
		\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...
1168
1169
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1170
			ManagerEvent::EVENT_APP_UPDATE, $appId
1171
		));
1172
1173
		return true;
1174
	}
1175
1176
	/**
1177
	 * @param string $appId
1178
	 * @param string[] $steps
1179
	 * @throws \OC\NeedsUpdateException
1180
	 */
1181
	public static function executeRepairSteps($appId, array $steps) {
1182
		if (empty($steps)) {
1183
			return;
1184
		}
1185
		// load the app
1186
		self::loadApp($appId, false);
1187
1188
		$dispatcher = OC::$server->getEventDispatcher();
1189
1190
		// load the steps
1191
		$r = new Repair([], $dispatcher);
1192
		foreach ($steps as $step) {
1193
			try {
1194
				$r->addStep($step);
1195
			} catch (Exception $ex) {
1196
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1197
				\OC::$server->getLogger()->logException($ex);
1198
			}
1199
		}
1200
		// run the steps
1201
		$r->run();
1202
	}
1203
1204
	public static function setupBackgroundJobs(array $jobs) {
1205
		$queue = \OC::$server->getJobList();
1206
		foreach ($jobs as $job) {
1207
			$queue->add($job);
1208
		}
1209
	}
1210
1211
	/**
1212
	 * @param string $appId
1213
	 * @param string[] $steps
1214
	 */
1215
	private static function setupLiveMigrations($appId, array $steps) {
1216
		$queue = \OC::$server->getJobList();
1217
		foreach ($steps as $step) {
1218
			$queue->add('OC\Migration\BackgroundRepair', [
1219
				'app' => $appId,
1220
				'step' => $step]);
1221
		}
1222
	}
1223
1224
	/**
1225
	 * @param string $appId
1226
	 * @return \OC\Files\View|false
1227
	 */
1228
	public static function getStorage($appId) {
1229
		if (OC_App::isEnabled($appId)) { //sanity check
1230
			if (OC_User::isLoggedIn()) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_User::isLoggedIn() has been deprecated with message: use \OC::$server->getUserSession()->isLoggedIn()

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...
1231
				$view = new \OC\Files\View('/' . OC_User::getUser());
1232
				if (!$view->file_exists($appId)) {
1233
					$view->mkdir($appId);
1234
				}
1235
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1236
			} else {
1237
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1238
				return false;
1239
			}
1240
		} else {
1241
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1242
			return false;
1243
		}
1244
	}
1245
1246
	protected static function findBestL10NOption($options, $lang) {
1247
		$fallback = $similarLangFallback = $englishFallback = false;
1248
1249
		$lang = strtolower($lang);
1250
		$similarLang = $lang;
1251
		if (strpos($similarLang, '_')) {
1252
			// For "de_DE" we want to find "de" and the other way around
1253
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1254
		}
1255
1256
		foreach ($options as $option) {
1257
			if (is_array($option)) {
1258
				if ($fallback === false) {
1259
					$fallback = $option['@value'];
1260
				}
1261
1262
				if (!isset($option['@attributes']['lang'])) {
1263
					continue;
1264
				}
1265
1266
				$attributeLang = strtolower($option['@attributes']['lang']);
1267
				if ($attributeLang === $lang) {
1268
					return $option['@value'];
1269
				}
1270
1271
				if ($attributeLang === $similarLang) {
1272
					$similarLangFallback = $option['@value'];
1273
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1274
					if ($similarLangFallback === false) {
1275
						$similarLangFallback =  $option['@value'];
1276
					}
1277
				}
1278
			} else {
1279
				$englishFallback = $option;
1280
			}
1281
		}
1282
1283
		if ($similarLangFallback !== false) {
1284
			return $similarLangFallback;
1285
		} else if ($englishFallback !== false) {
1286
			return $englishFallback;
1287
		}
1288
		return (string) $fallback;
1289
	}
1290
1291
	/**
1292
	 * parses the app data array and enhanced the 'description' value
1293
	 *
1294
	 * @param array $data the app data
1295
	 * @param string $lang
1296
	 * @return array improved app data
1297
	 */
1298
	public static function parseAppInfo(array $data, $lang = null) {
1299
1300 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...
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...
1301
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1302
		}
1303 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...
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...
1304
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1305
		}
1306
		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...
1307
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1308
		} else if (isset($data['description']) && is_string($data['description'])) {
1309
			$data['description'] = trim($data['description']);
1310
		} else  {
1311
			$data['description'] = '';
1312
		}
1313
1314
		return $data;
1315
	}
1316
1317
	/**
1318
	 * @param \OCP\IConfig $config
1319
	 * @param \OCP\IL10N $l
1320
	 * @param array $info
1321
	 * @throws \Exception
1322
	 */
1323
	protected static function checkAppDependencies($config, $l, $info) {
1324
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1325
		$missing = $dependencyAnalyzer->analyze($info);
1326
		if (!empty($missing)) {
1327
			$missingMsg = join(PHP_EOL, $missing);
1328
			throw new \Exception(
1329
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1330
					[$info['name'], $missingMsg]
1331
				)
1332
			);
1333
		}
1334
	}
1335
}
1336