Completed
Push — master ( 8f38ad...4035d6 )
by Joas
13:12 queued 06:02
created

OC_App::loadApp()   C

Complexity

Conditions 12
Paths 38

Size

Total Lines 43
Code Lines 24

Duplication

Lines 15
Ratio 34.88 %

Importance

Changes 0
Metric Value
cc 12
eloc 24
nc 38
nop 2
dl 15
loc 43
rs 5.1612
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
		}
277
278
		\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...
279
	}
280
281
	/**
282
	 * check if app is shipped
283
	 *
284
	 * @param string $appId the id of the app to check
285
	 * @return bool
286
	 *
287
	 * Check if an app that is installed is a shipped app or installed from the appstore.
288
	 */
289
	public static function isShipped($appId) {
290
		return \OC::$server->getAppManager()->isShipped($appId);
291
	}
292
293
	/**
294
	 * get all enabled apps
295
	 */
296
	protected static $enabledAppsCache = array();
297
298
	/**
299
	 * Returns apps enabled for the current user.
300
	 *
301
	 * @param bool $forceRefresh whether to refresh the cache
302
	 * @param bool $all whether to return apps for all users, not only the
303
	 * currently logged in one
304
	 * @return string[]
305
	 */
306
	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...
307
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
308
			return array();
309
		}
310
		// in incognito mode or when logged out, $user will be false,
311
		// which is also the case during an upgrade
312
		$appManager = \OC::$server->getAppManager();
313
		if ($all) {
314
			$user = null;
315
		} else {
316
			$user = \OC::$server->getUserSession()->getUser();
317
		}
318
319
		if (is_null($user)) {
320
			$apps = $appManager->getInstalledApps();
321
		} else {
322
			$apps = $appManager->getEnabledAppsForUser($user);
323
		}
324
		$apps = array_filter($apps, function ($app) {
325
			return $app !== 'files';//we add this manually
326
		});
327
		sort($apps);
328
		array_unshift($apps, 'files');
329
		return $apps;
330
	}
331
332
	/**
333
	 * checks whether or not an app is enabled
334
	 *
335
	 * @param string $app app
336
	 * @return bool
337
	 *
338
	 * This function checks whether or not an app is enabled.
339
	 */
340
	public static function isEnabled($app) {
341
		return \OC::$server->getAppManager()->isEnabledForUser($app);
342
	}
343
344
	/**
345
	 * enables an app
346
	 *
347
	 * @param string $appId
348
	 * @param array $groups (optional) when set, only these groups will have access to the app
349
	 * @throws \Exception
350
	 * @return void
351
	 *
352
	 * This function set an app as enabled in appconfig.
353
	 */
354
	public function enable($appId,
355
						   $groups = null) {
356
		self::$enabledAppsCache = []; // flush
357
		$l = \OC::$server->getL10N('core');
358
		$config = \OC::$server->getConfig();
359
360
		// Check if app is already downloaded
361
		$installer = new Installer(
362
			\OC::$server->getAppFetcher(),
363
			\OC::$server->getHTTPClientService(),
364
			\OC::$server->getTempManager(),
365
			\OC::$server->getLogger()
366
		);
367
		$isDownloaded = $installer->isDownloaded($appId);
368
369
		if(!$isDownloaded) {
370
			$installer->downloadApp($appId);
371
		}
372
373
		if (!Installer::isInstalled($appId)) {
374
			$appId = self::installApp(
375
				$appId,
376
				$config,
377
				$l
378
			);
379
			$appPath = self::getAppPath($appId);
380
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 379 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...
381
			$installer->installApp($appId);
382
		} else {
383
			// check for required dependencies
384
			$info = self::getAppInfo($appId);
385
			self::checkAppDependencies($config, $l, $info);
386
			$appPath = self::getAppPath($appId);
387
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 386 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...
388
			$installer->installApp($appId);
389
		}
390
391
		$appManager = \OC::$server->getAppManager();
392
		if (!is_null($groups)) {
393
			$groupManager = \OC::$server->getGroupManager();
394
			$groupsList = [];
395
			foreach ($groups as $group) {
396
				$groupItem = $groupManager->get($group);
397
				if ($groupItem instanceof \OCP\IGroup) {
398
					$groupsList[] = $groupManager->get($group);
399
				}
400
			}
401
			$appManager->enableAppForGroups($appId, $groupsList);
402
		} else {
403
			$appManager->enableApp($appId);
404
		}
405
406
		$info = self::getAppInfo($appId);
407 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...
408
			$appPath = self::getAppPath($appId);
409
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 408 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...
410
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
411
		}
412
	}
413
414
	/**
415
	 * @param string $app
416
	 * @return bool
417
	 */
418
	public static function removeApp($app) {
419
		if (self::isShipped($app)) {
420
			return false;
421
		}
422
423
		$installer = new Installer(
424
			\OC::$server->getAppFetcher(),
425
			\OC::$server->getHTTPClientService(),
426
			\OC::$server->getTempManager(),
427
			\OC::$server->getLogger()
428
		);
429
		return $installer->removeApp($app);
430
	}
431
432
	/**
433
	 * This function set an app as disabled in appconfig.
434
	 *
435
	 * @param string $app app
436
	 * @throws Exception
437
	 */
438
	public static function disable($app) {
439
		// flush
440
		self::$enabledAppsCache = array();
441
442
		// run uninstall steps
443
		$appData = OC_App::getAppInfo($app);
444
		if (!is_null($appData)) {
445
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
446
		}
447
448
		// emit disable hook - needed anymore ?
449
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
450
451
		// finally disable it
452
		$appManager = \OC::$server->getAppManager();
453
		$appManager->disableApp($app);
454
	}
455
456
	/**
457
	 * Returns the Settings Navigation
458
	 *
459
	 * @return string[]
460
	 *
461
	 * This function returns an array containing all settings pages added. The
462
	 * entries are sorted by the key 'order' ascending.
463
	 */
464
	public static function getSettingsNavigation() {
465
		$l = \OC::$server->getL10N('lib');
466
		$urlGenerator = \OC::$server->getURLGenerator();
467
468
		$settings = array();
469
		// by default, settings only contain the help menu
470
		if (\OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true)) {
471
			$settings = array(
472
				array(
473
					"id" => "help",
474
					"order" => 4,
475
					"href" => $urlGenerator->linkToRoute('settings_help'),
476
					"name" => $l->t("Help"),
477
					"icon" => $urlGenerator->imagePath("settings", "help.svg")
478
				)
479
			);
480
		}
481
482
		// if the user is logged-in
483
		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...
484
			// personal menu
485
			$settings[] = array(
486
				"id" => "personal",
487
				"order" => 1,
488
				"href" => $urlGenerator->linkToRoute('settings_personal'),
489
				"name" => $l->t("Personal"),
490
				"icon" => $urlGenerator->imagePath("settings", "personal.svg")
491
			);
492
493
			//SubAdmins are also allowed to access user management
494
			$userObject = \OC::$server->getUserSession()->getUser();
495
			$isSubAdmin = false;
496
			if($userObject !== null) {
497
				$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
498
			}
499
			if ($isSubAdmin) {
500
				// admin users menu
501
				$settings[] = array(
502
					"id" => "core_users",
503
					"order" => 3,
504
					"href" => $urlGenerator->linkToRoute('settings_users'),
505
					"name" => $l->t("Users"),
506
					"icon" => $urlGenerator->imagePath("settings", "users.svg")
507
				);
508
			}
509
510
			// if the user is an admin
511
			if (OC_User::isAdminUser(OC_User::getUser())) {
512
				// admin settings
513
				$settings[] = array(
514
					"id" => "admin",
515
					"order" => 2,
516
					"href" => $urlGenerator->linkToRoute('settings.AdminSettings.index'),
517
					"name" => $l->t("Admin"),
518
					"icon" => $urlGenerator->imagePath("settings", "admin.svg")
519
				);
520
			}
521
		}
522
523
		$navigation = self::proceedNavigation($settings);
524
		return $navigation;
525
	}
526
527
	// This is private as well. It simply works, so don't ask for more details
528
	private static function proceedNavigation($list) {
529
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
530
		foreach ($list as &$navEntry) {
531
			if ($navEntry['id'] == $activeApp) {
532
				$navEntry['active'] = true;
533
			} else {
534
				$navEntry['active'] = false;
535
			}
536
		}
537
		unset($navEntry);
538
539
		usort($list, function($a, $b) {
540
			if (isset($a['order']) && isset($b['order'])) {
541
				return ($a['order'] < $b['order']) ? -1 : 1;
542
			} else if (isset($a['order']) || isset($b['order'])) {
543
				return isset($a['order']) ? -1 : 1;
544
			} else {
545
				return ($a['name'] < $b['name']) ? -1 : 1;
546
			}
547
		});
548
549
		return $list;
550
	}
551
552
	/**
553
	 * Get the path where to install apps
554
	 *
555
	 * @return string|false
556
	 */
557
	public static function getInstallPath() {
558
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
559
			return false;
560
		}
561
562
		foreach (OC::$APPSROOTS as $dir) {
563
			if (isset($dir['writable']) && $dir['writable'] === true) {
564
				return $dir['path'];
565
			}
566
		}
567
568
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
569
		return null;
570
	}
571
572
573
	/**
574
	 * search for an app in all app-directories
575
	 *
576
	 * @param string $appId
577
	 * @return false|string
578
	 */
579
	public static function findAppInDirectories($appId) {
580
		$sanitizedAppId = self::cleanAppId($appId);
581
		if($sanitizedAppId !== $appId) {
582
			return false;
583
		}
584
		static $app_dir = array();
585
586
		if (isset($app_dir[$appId])) {
587
			return $app_dir[$appId];
588
		}
589
590
		$possibleApps = array();
591
		foreach (OC::$APPSROOTS as $dir) {
592
			if (file_exists($dir['path'] . '/' . $appId)) {
593
				$possibleApps[] = $dir;
594
			}
595
		}
596
597
		if (empty($possibleApps)) {
598
			return false;
599
		} elseif (count($possibleApps) === 1) {
600
			$dir = array_shift($possibleApps);
601
			$app_dir[$appId] = $dir;
602
			return $dir;
603
		} else {
604
			$versionToLoad = array();
605
			foreach ($possibleApps as $possibleApp) {
606
				$version = self::getAppVersionByPath($possibleApp['path']);
607
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
608
					$versionToLoad = array(
609
						'dir' => $possibleApp,
610
						'version' => $version,
611
					);
612
				}
613
			}
614
			$app_dir[$appId] = $versionToLoad['dir'];
615
			return $versionToLoad['dir'];
616
			//TODO - write test
617
		}
618
	}
619
620
	/**
621
	 * Get the directory for the given app.
622
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
623
	 *
624
	 * @param string $appId
625
	 * @return string|false
626
	 */
627
	public static function getAppPath($appId) {
628
		if ($appId === null || trim($appId) === '') {
629
			return false;
630
		}
631
632
		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...
633
			return $dir['path'] . '/' . $appId;
634
		}
635
		return false;
636
	}
637
638
	/**
639
	 * Get the path for the given app on the access
640
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
641
	 *
642
	 * @param string $appId
643
	 * @return string|false
644
	 */
645
	public static function getAppWebPath($appId) {
646
		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...
647
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
648
		}
649
		return false;
650
	}
651
652
	/**
653
	 * get the last version of the app from appinfo/info.xml
654
	 *
655
	 * @param string $appId
656
	 * @return string
657
	 */
658
	public static function getAppVersion($appId) {
659
		if (!isset(self::$appVersion[$appId])) {
660
			$file = self::getAppPath($appId);
661
			self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
662
		}
663
		return self::$appVersion[$appId];
664
	}
665
666
	/**
667
	 * get app's version based on it's path
668
	 *
669
	 * @param string $path
670
	 * @return string
671
	 */
672
	public static function getAppVersionByPath($path) {
673
		$infoFile = $path . '/appinfo/info.xml';
674
		$appData = self::getAppInfo($infoFile, true);
675
		return isset($appData['version']) ? $appData['version'] : '';
676
	}
677
678
679
	/**
680
	 * Read all app metadata from the info.xml file
681
	 *
682
	 * @param string $appId id of the app or the path of the info.xml file
683
	 * @param bool $path
684
	 * @param string $lang
685
	 * @return array|null
686
	 * @note all data is read from info.xml, not just pre-defined fields
687
	 */
688
	public static function getAppInfo($appId, $path = false, $lang = null) {
689
		if ($path) {
690
			$file = $appId;
691
		} else {
692
			if ($lang === null && isset(self::$appInfo[$appId])) {
693
				return self::$appInfo[$appId];
694
			}
695
			$appPath = self::getAppPath($appId);
696
			if($appPath === false) {
697
				return null;
698
			}
699
			$file = $appPath . '/appinfo/info.xml';
700
		}
701
702
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
703
		$data = $parser->parse($file);
704
705
		if (is_array($data)) {
706
			$data = OC_App::parseAppInfo($data, $lang);
707
		}
708
		if(isset($data['ocsid'])) {
709
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
710
			if($storedId !== '' && $storedId !== $data['ocsid']) {
711
				$data['ocsid'] = $storedId;
712
			}
713
		}
714
715
		if ($lang === null) {
716
			self::$appInfo[$appId] = $data;
717
		}
718
719
		return $data;
720
	}
721
722
	/**
723
	 * Returns the navigation
724
	 *
725
	 * @return array
726
	 *
727
	 * This function returns an array containing all entries added. The
728
	 * entries are sorted by the key 'order' ascending. Additional to the keys
729
	 * given for each app the following keys exist:
730
	 *   - active: boolean, signals if the user is on this navigation entry
731
	 */
732
	public static function getNavigation() {
733
		$entries = OC::$server->getNavigationManager()->getAll();
734
		$navigation = self::proceedNavigation($entries);
735
		return $navigation;
736
	}
737
738
	/**
739
	 * get the id of loaded app
740
	 *
741
	 * @return string
742
	 */
743
	public static function getCurrentApp() {
744
		$request = \OC::$server->getRequest();
745
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
746
		$topFolder = substr($script, 0, strpos($script, '/'));
747
		if (empty($topFolder)) {
748
			$path_info = $request->getPathInfo();
749
			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...
750
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
751
			}
752
		}
753
		if ($topFolder == 'apps') {
754
			$length = strlen($topFolder);
755
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
756
		} else {
757
			return $topFolder;
758
		}
759
	}
760
761
	/**
762
	 * @param string $type
763
	 * @return array
764
	 */
765
	public static function getForms($type) {
766
		$forms = array();
767
		switch ($type) {
768
			case 'admin':
769
				$source = self::$adminForms;
770
				break;
771
			case 'personal':
772
				$source = self::$personalForms;
773
				break;
774
			default:
775
				return array();
776
		}
777
		foreach ($source as $form) {
778
			$forms[] = include $form;
779
		}
780
		return $forms;
781
	}
782
783
	/**
784
	 * register an admin form to be shown
785
	 *
786
	 * @param string $app
787
	 * @param string $page
788
	 */
789
	public static function registerAdmin($app, $page) {
790
		self::$adminForms[] = $app . '/' . $page . '.php';
791
	}
792
793
	/**
794
	 * register a personal form to be shown
795
	 * @param string $app
796
	 * @param string $page
797
	 */
798
	public static function registerPersonal($app, $page) {
799
		self::$personalForms[] = $app . '/' . $page . '.php';
800
	}
801
802
	/**
803
	 * @param array $entry
804
	 */
805
	public static function registerLogIn(array $entry) {
806
		self::$altLogin[] = $entry;
807
	}
808
809
	/**
810
	 * @return array
811
	 */
812
	public static function getAlternativeLogIns() {
813
		return self::$altLogin;
814
	}
815
816
	/**
817
	 * get a list of all apps in the apps folder
818
	 *
819
	 * @return array an array of app names (string IDs)
820
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
821
	 */
822
	public static function getAllApps() {
823
824
		$apps = array();
825
826
		foreach (OC::$APPSROOTS as $apps_dir) {
827
			if (!is_readable($apps_dir['path'])) {
828
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
829
				continue;
830
			}
831
			$dh = opendir($apps_dir['path']);
832
833
			if (is_resource($dh)) {
834
				while (($file = readdir($dh)) !== false) {
835
836
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
837
838
						$apps[] = $file;
839
					}
840
				}
841
			}
842
		}
843
844
		return $apps;
845
	}
846
847
	/**
848
	 * List all apps, this is used in apps.php
849
	 *
850
	 * @return array
851
	 */
852
	public function listAllApps() {
853
		$installedApps = OC_App::getAllApps();
854
855
		//we don't want to show configuration for these
856
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
857
		$appList = array();
858
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
859
		$urlGenerator = \OC::$server->getURLGenerator();
860
861
		foreach ($installedApps as $app) {
862
			if (array_search($app, $blacklist) === false) {
863
864
				$info = OC_App::getAppInfo($app, false, $langCode);
865
				if (!is_array($info)) {
866
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
867
					continue;
868
				}
869
870
				if (!isset($info['name'])) {
871
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
872
					continue;
873
				}
874
875
				$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...
876
				$info['groups'] = null;
877
				if ($enabled === 'yes') {
878
					$active = true;
879
				} else if ($enabled === 'no') {
880
					$active = false;
881
				} else {
882
					$active = true;
883
					$info['groups'] = $enabled;
884
				}
885
886
				$info['active'] = $active;
887
888
				if (self::isShipped($app)) {
889
					$info['internal'] = true;
890
					$info['level'] = self::officialApp;
891
					$info['removable'] = false;
892
				} else {
893
					$info['internal'] = false;
894
					$info['removable'] = true;
895
				}
896
897
				$appPath = self::getAppPath($app);
898
				if($appPath !== false) {
899
					$appIcon = $appPath . '/img/' . $app . '.svg';
900
					if (file_exists($appIcon)) {
901
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
902
						$info['previewAsIcon'] = true;
903
					} else {
904
						$appIcon = $appPath . '/img/app.svg';
905
						if (file_exists($appIcon)) {
906
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
907
							$info['previewAsIcon'] = true;
908
						}
909
					}
910
				}
911
				// fix documentation
912
				if (isset($info['documentation']) && is_array($info['documentation'])) {
913
					foreach ($info['documentation'] as $key => $url) {
914
						// If it is not an absolute URL we assume it is a key
915
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
916
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
917
							$url = $urlGenerator->linkToDocs($url);
918
						}
919
920
						$info['documentation'][$key] = $url;
921
					}
922
				}
923
924
				$info['version'] = OC_App::getAppVersion($app);
925
				$appList[] = $info;
926
			}
927
		}
928
929
		return $appList;
930
	}
931
932
	/**
933
	 * Returns the internal app ID or false
934
	 * @param string $ocsID
935
	 * @return string|false
936
	 */
937
	public static function getInternalAppIdByOcs($ocsID) {
938
		if(is_numeric($ocsID)) {
939
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
940
			if(array_search($ocsID, $idArray)) {
941
				return array_search($ocsID, $idArray);
942
			}
943
		}
944
		return false;
945
	}
946
947
	public static function shouldUpgrade($app) {
948
		$versions = self::getAppVersions();
949
		$currentVersion = OC_App::getAppVersion($app);
950
		if ($currentVersion && isset($versions[$app])) {
951
			$installedVersion = $versions[$app];
952
			if (!version_compare($currentVersion, $installedVersion, '=')) {
953
				return true;
954
			}
955
		}
956
		return false;
957
	}
958
959
	/**
960
	 * Adjust the number of version parts of $version1 to match
961
	 * the number of version parts of $version2.
962
	 *
963
	 * @param string $version1 version to adjust
964
	 * @param string $version2 version to take the number of parts from
965
	 * @return string shortened $version1
966
	 */
967
	private static function adjustVersionParts($version1, $version2) {
968
		$version1 = explode('.', $version1);
969
		$version2 = explode('.', $version2);
970
		// reduce $version1 to match the number of parts in $version2
971
		while (count($version1) > count($version2)) {
972
			array_pop($version1);
973
		}
974
		// if $version1 does not have enough parts, add some
975
		while (count($version1) < count($version2)) {
976
			$version1[] = '0';
977
		}
978
		return implode('.', $version1);
979
	}
980
981
	/**
982
	 * Check whether the current ownCloud version matches the given
983
	 * application's version requirements.
984
	 *
985
	 * The comparison is made based on the number of parts that the
986
	 * app info version has. For example for ownCloud 6.0.3 if the
987
	 * app info version is expecting version 6.0, the comparison is
988
	 * made on the first two parts of the ownCloud version.
989
	 * This means that it's possible to specify "requiremin" => 6
990
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
991
	 *
992
	 * @param string $ocVersion ownCloud version to check against
993
	 * @param array $appInfo app info (from xml)
994
	 *
995
	 * @return boolean true if compatible, otherwise false
996
	 */
997
	public static function isAppCompatible($ocVersion, $appInfo) {
998
		$requireMin = '';
999
		$requireMax = '';
1000
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
1001
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
1002 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...
1003
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
1004
		} else if (isset($appInfo['requiremin'])) {
1005
			$requireMin = $appInfo['requiremin'];
1006
		} else if (isset($appInfo['require'])) {
1007
			$requireMin = $appInfo['require'];
1008
		}
1009
1010
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
1011
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
1012 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...
1013
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
1014
		} else if (isset($appInfo['requiremax'])) {
1015
			$requireMax = $appInfo['requiremax'];
1016
		}
1017
1018
		if (is_array($ocVersion)) {
1019
			$ocVersion = implode('.', $ocVersion);
1020
		}
1021
1022 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...
1023
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
1024
		) {
1025
1026
			return false;
1027
		}
1028
1029 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...
1030
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
1031
		) {
1032
			return false;
1033
		}
1034
1035
		return true;
1036
	}
1037
1038
	/**
1039
	 * get the installed version of all apps
1040
	 */
1041
	public static function getAppVersions() {
1042
		static $versions;
1043
1044
		if(!$versions) {
1045
			$appConfig = \OC::$server->getAppConfig();
1046
			$versions = $appConfig->getValues(false, 'installed_version');
1047
		}
1048
		return $versions;
1049
	}
1050
1051
	/**
1052
	 * @param string $app
1053
	 * @param \OCP\IConfig $config
1054
	 * @param \OCP\IL10N $l
1055
	 * @return bool
1056
	 *
1057
	 * @throws Exception if app is not compatible with this version of ownCloud
1058
	 * @throws Exception if no app-name was specified
1059
	 */
1060
	public function installApp($app,
1061
							   \OCP\IConfig $config,
1062
							   \OCP\IL10N $l) {
1063
		if ($app !== false) {
1064
			// check if the app is compatible with this version of ownCloud
1065
			$info = self::getAppInfo($app);
1066
			if(!is_array($info)) {
1067
				throw new \Exception(
1068
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1069
						[$info['name']]
1070
					)
1071
				);
1072
			}
1073
1074
			$version = \OCP\Util::getVersion();
1075
			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...
1076
				throw new \Exception(
1077
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1078
						array($info['name'])
1079
					)
1080
				);
1081
			}
1082
1083
			// check for required dependencies
1084
			self::checkAppDependencies($config, $l, $info);
1085
1086
			$config->setAppValue($app, 'enabled', 'yes');
1087
			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...
1088
				$config->setAppValue($app, 'ocsid', $appData['id']);
1089
			}
1090
1091 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...
1092
				$appPath = self::getAppPath($app);
1093
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1092 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...
1094
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1095
			}
1096
1097
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1098
		} else {
1099
			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...
1100
				throw new \Exception($l->t("No app name specified"));
1101
			} else {
1102
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1103
			}
1104
		}
1105
1106
		return $app;
1107
	}
1108
1109
	/**
1110
	 * update the database for the app and call the update script
1111
	 *
1112
	 * @param string $appId
1113
	 * @return bool
1114
	 */
1115
	public static function updateApp($appId) {
1116
		$appPath = self::getAppPath($appId);
1117
		if($appPath === false) {
1118
			return false;
1119
		}
1120
		$appData = self::getAppInfo($appId);
1121
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1122
		if (file_exists($appPath . '/appinfo/database.xml')) {
1123
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1124
		}
1125
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1126
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1127
		unset(self::$appVersion[$appId]);
1128
		// run upgrade code
1129
		if (file_exists($appPath . '/appinfo/update.php')) {
1130
			self::loadApp($appId, false);
1131
			include $appPath . '/appinfo/update.php';
1132
		}
1133
		self::setupBackgroundJobs($appData['background-jobs']);
1134 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...
1135
			$appPath = self::getAppPath($appId);
1136
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1135 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...
1137
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1138
		}
1139
1140
		//set remote/public handlers
1141
		if (array_key_exists('ocsid', $appData)) {
1142
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1143 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...
1144
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1145
		}
1146
		foreach ($appData['remote'] as $name => $path) {
1147
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1148
		}
1149 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...
1150
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1151
		}
1152
1153
		self::setAppTypes($appId);
1154
1155
		$version = \OC_App::getAppVersion($appId);
1156
		\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...
1157
1158
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1159
			ManagerEvent::EVENT_APP_UPDATE, $appId
1160
		));
1161
1162
		return true;
1163
	}
1164
1165
	/**
1166
	 * @param string $appId
1167
	 * @param string[] $steps
1168
	 * @throws \OC\NeedsUpdateException
1169
	 */
1170
	public static function executeRepairSteps($appId, array $steps) {
1171
		if (empty($steps)) {
1172
			return;
1173
		}
1174
		// load the app
1175
		self::loadApp($appId, false);
1176
1177
		$dispatcher = OC::$server->getEventDispatcher();
1178
1179
		// load the steps
1180
		$r = new Repair([], $dispatcher);
1181
		foreach ($steps as $step) {
1182
			try {
1183
				$r->addStep($step);
1184
			} catch (Exception $ex) {
1185
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1186
				\OC::$server->getLogger()->logException($ex);
1187
			}
1188
		}
1189
		// run the steps
1190
		$r->run();
1191
	}
1192
1193
	public static function setupBackgroundJobs(array $jobs) {
1194
		$queue = \OC::$server->getJobList();
1195
		foreach ($jobs as $job) {
1196
			$queue->add($job);
1197
		}
1198
	}
1199
1200
	/**
1201
	 * @param string $appId
1202
	 * @param string[] $steps
1203
	 */
1204
	private static function setupLiveMigrations($appId, array $steps) {
1205
		$queue = \OC::$server->getJobList();
1206
		foreach ($steps as $step) {
1207
			$queue->add('OC\Migration\BackgroundRepair', [
1208
				'app' => $appId,
1209
				'step' => $step]);
1210
		}
1211
	}
1212
1213
	/**
1214
	 * @param string $appId
1215
	 * @return \OC\Files\View|false
1216
	 */
1217
	public static function getStorage($appId) {
1218
		if (OC_App::isEnabled($appId)) { //sanity check
1219
			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...
1220
				$view = new \OC\Files\View('/' . OC_User::getUser());
1221
				if (!$view->file_exists($appId)) {
1222
					$view->mkdir($appId);
1223
				}
1224
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1225
			} else {
1226
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1227
				return false;
1228
			}
1229
		} else {
1230
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1231
			return false;
1232
		}
1233
	}
1234
1235
	protected static function findBestL10NOption($options, $lang) {
1236
		$fallback = $similarLangFallback = $englishFallback = false;
1237
1238
		$lang = strtolower($lang);
1239
		$similarLang = $lang;
1240
		if (strpos($similarLang, '_')) {
1241
			// For "de_DE" we want to find "de" and the other way around
1242
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1243
		}
1244
1245
		foreach ($options as $option) {
1246
			if (is_array($option)) {
1247
				if ($fallback === false) {
1248
					$fallback = $option['@value'];
1249
				}
1250
1251
				if (!isset($option['@attributes']['lang'])) {
1252
					continue;
1253
				}
1254
1255
				$attributeLang = strtolower($option['@attributes']['lang']);
1256
				if ($attributeLang === $lang) {
1257
					return $option['@value'];
1258
				}
1259
1260
				if ($attributeLang === $similarLang) {
1261
					$similarLangFallback = $option['@value'];
1262
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1263
					if ($similarLangFallback === false) {
1264
						$similarLangFallback =  $option['@value'];
1265
					}
1266
				}
1267
			} else {
1268
				$englishFallback = $option;
1269
			}
1270
		}
1271
1272
		if ($similarLangFallback !== false) {
1273
			return $similarLangFallback;
1274
		} else if ($englishFallback !== false) {
1275
			return $englishFallback;
1276
		}
1277
		return (string) $fallback;
1278
	}
1279
1280
	/**
1281
	 * parses the app data array and enhanced the 'description' value
1282
	 *
1283
	 * @param array $data the app data
1284
	 * @param string $lang
1285
	 * @return array improved app data
1286
	 */
1287
	public static function parseAppInfo(array $data, $lang = null) {
1288
1289 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...
1290
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1291
		}
1292 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...
1293
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1294
		}
1295 View Code Duplication
		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...
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...
1296
			$data['description'] = self::findBestL10NOption($data['description'], $lang);
1297
		}
1298
1299
		// just modify the description if it is available
1300
		// otherwise this will create a $data element with an empty 'description'
1301
		if (isset($data['description'])) {
1302
			if (is_string($data['description'])) {
1303
				// sometimes the description contains line breaks and they are then also
1304
				// shown in this way in the app management which isn't wanted as HTML
1305
				// manages line breaks itself
1306
1307
				// first of all we split on empty lines
1308
				$paragraphs = preg_split("!\n[[:space:]]*\n!mu", $data['description']);
1309
1310
				$result = [];
1311
				foreach ($paragraphs as $value) {
1312
					// replace multiple whitespace (tabs, space, newlines) inside a paragraph
1313
					// with a single space - also trims whitespace
1314
					$result[] = trim(preg_replace('![[:space:]]+!mu', ' ', $value));
1315
				}
1316
1317
				// join the single paragraphs with a empty line in between
1318
				$data['description'] = implode("\n\n", $result);
1319
1320
			} else {
1321
				$data['description'] = '';
1322
			}
1323
		}
1324
1325
		return $data;
1326
	}
1327
1328
	/**
1329
	 * @param $config
1330
	 * @param $l
1331
	 * @param $info
1332
	 * @throws Exception
1333
	 */
1334
	protected static function checkAppDependencies($config, $l, $info) {
1335
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1336
		$missing = $dependencyAnalyzer->analyze($info);
1337
		if (!empty($missing)) {
1338
			$missingMsg = join(PHP_EOL, $missing);
1339
			throw new \Exception(
1340
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1341
					[$info['name'], $missingMsg]
1342
				)
1343
			);
1344
		}
1345
	}
1346
}
1347