Completed
Push — master ( 51da04...db94b5 )
by Lukas
117:18 queued 98:55
created

OC_App::getHeaderNavigation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A OC_App::registerAdmin() 0 3 1
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\Repair;
55
use OCP\App\ManagerEvent;
56
57
/**
58
 * This class manages the apps. It allows them to register and integrate in the
59
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
60
 * upgrading and removing apps.
61
 */
62
class OC_App {
63
	static private $appVersion = [];
64
	static private $adminForms = array();
65
	static private $personalForms = array();
66
	static private $appInfo = array();
67
	static private $appTypes = array();
68
	static private $loadedApps = array();
69
	static private $altLogin = array();
70
	static private $alreadyRegistered = [];
71
	const officialApp = 200;
72
73
	/**
74
	 * clean the appId
75
	 *
76
	 * @param string|boolean $app AppId that needs to be cleaned
77
	 * @return string
78
	 */
79
	public static function cleanAppId($app) {
80
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
81
	}
82
83
	/**
84
	 * Check if an app is loaded
85
	 *
86
	 * @param string $app
87
	 * @return bool
88
	 */
89
	public static function isAppLoaded($app) {
90
		return in_array($app, self::$loadedApps, true);
91
	}
92
93
	/**
94
	 * loads all apps
95
	 *
96
	 * @param string[] | string | null $types
97
	 * @return bool
98
	 *
99
	 * This function walks through the ownCloud directory and loads all apps
100
	 * it can find. A directory contains an app if the file /appinfo/info.xml
101
	 * exists.
102
	 *
103
	 * if $types is set, only apps of those types will be loaded
104
	 */
105
	public static function loadApps($types = null) {
106
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
107
			return false;
108
		}
109
		// Load the enabled apps here
110
		$apps = self::getEnabledApps();
111
112
		// Add each apps' folder as allowed class path
113
		foreach($apps as $app) {
114
			$path = self::getAppPath($app);
115
			if($path !== false) {
116
				self::registerAutoloading($app, $path);
117
			}
118
		}
119
120
		// prevent app.php from printing output
121
		ob_start();
122
		foreach ($apps as $app) {
123
			if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
124
				self::loadApp($app);
125
			}
126
		}
127
		ob_end_clean();
128
129
		return true;
130
	}
131
132
	/**
133
	 * load a single app
134
	 *
135
	 * @param string $app
136
	 */
137
	public static function loadApp($app) {
138
		self::$loadedApps[] = $app;
139
		$appPath = self::getAppPath($app);
140
		if($appPath === false) {
141
			return;
142
		}
143
144
		// in case someone calls loadApp() directly
145
		self::registerAutoloading($app, $appPath);
146
147
		if (is_file($appPath . '/appinfo/app.php')) {
148
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
149
			self::requireAppFile($app);
150
			if (self::isType($app, array('authentication'))) {
151
				// since authentication apps affect the "is app enabled for group" check,
152
				// the enabled apps cache needs to be cleared to make sure that the
153
				// next time getEnableApps() is called it will also include apps that were
154
				// enabled for groups
155
				self::$enabledAppsCache = array();
156
			}
157
			\OC::$server->getEventLogger()->end('load_app_' . $app);
158
		}
159
160
		$info = self::getAppInfo($app);
161 View Code Duplication
		if (!empty($info['activity']['filters'])) {
162
			foreach ($info['activity']['filters'] as $filter) {
163
				\OC::$server->getActivityManager()->registerFilter($filter);
164
			}
165
		}
166 View Code Duplication
		if (!empty($info['activity']['settings'])) {
167
			foreach ($info['activity']['settings'] as $setting) {
168
				\OC::$server->getActivityManager()->registerSetting($setting);
169
			}
170
		}
171 View Code Duplication
		if (!empty($info['activity']['providers'])) {
172
			foreach ($info['activity']['providers'] as $provider) {
173
				\OC::$server->getActivityManager()->registerProvider($provider);
174
			}
175
		}
176
	}
177
178
	/**
179
	 * @internal
180
	 * @param string $app
181
	 * @param string $path
182
	 */
183
	public static function registerAutoloading($app, $path) {
184
		$key = $app . '-' . $path;
185
		if(isset(self::$alreadyRegistered[$key])) {
186
			return;
187
		}
188
		self::$alreadyRegistered[$key] = true;
189
		// Register on PSR-4 composer autoloader
190
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
191
		\OC::$server->registerNamespace($app, $appNamespace);
192
		\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
193
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
194
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
195
		}
196
197
		// Register on legacy autoloader
198
		\OC::$loader->addValidRoot($path);
199
	}
200
201
	/**
202
	 * Load app.php from the given app
203
	 *
204
	 * @param string $app app name
205
	 */
206
	private static function requireAppFile($app) {
207
		try {
208
			// encapsulated here to avoid variable scope conflicts
209
			require_once $app . '/appinfo/app.php';
210
		} 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...
211
			\OC::$server->getLogger()->logException($ex);
212
			$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
213
			if (!in_array($app, $blacklist)) {
214
				self::disable($app);
215
			}
216
		}
217
	}
218
219
	/**
220
	 * check if an app is of a specific type
221
	 *
222
	 * @param string $app
223
	 * @param string|array $types
224
	 * @return bool
225
	 */
226
	public static function isType($app, $types) {
227
		if (is_string($types)) {
228
			$types = array($types);
229
		}
230
		$appTypes = self::getAppTypes($app);
231
		foreach ($types as $type) {
232
			if (array_search($type, $appTypes) !== false) {
233
				return true;
234
			}
235
		}
236
		return false;
237
	}
238
239
	/**
240
	 * get the types of an app
241
	 *
242
	 * @param string $app
243
	 * @return array
244
	 */
245
	private static function getAppTypes($app) {
246
		//load the cache
247
		if (count(self::$appTypes) == 0) {
248
			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...
249
		}
250
251
		if (isset(self::$appTypes[$app])) {
252
			return explode(',', self::$appTypes[$app]);
253
		} else {
254
			return array();
255
		}
256
	}
257
258
	/**
259
	 * read app types from info.xml and cache them in the database
260
	 */
261
	public static function setAppTypes($app) {
262
		$appData = self::getAppInfo($app);
263
		if(!is_array($appData)) {
264
			return;
265
		}
266
267
		if (isset($appData['types'])) {
268
			$appTypes = implode(',', $appData['types']);
269
		} else {
270
			$appTypes = '';
271
			$appData['types'] = [];
272
		}
273
274
		\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...
275
276
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
277
			$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...
278
			if ($enabled !== 'yes' && $enabled !== 'no') {
279
				\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...
280
			}
281
		}
282
	}
283
284
	/**
285
	 * check if app is shipped
286
	 *
287
	 * @param string $appId the id of the app to check
288
	 * @return bool
289
	 *
290
	 * Check if an app that is installed is a shipped app or installed from the appstore.
291
	 */
292
	public static function isShipped($appId) {
293
		return \OC::$server->getAppManager()->isShipped($appId);
294
	}
295
296
	/**
297
	 * get all enabled apps
298
	 */
299
	protected static $enabledAppsCache = array();
300
301
	/**
302
	 * Returns apps enabled for the current user.
303
	 *
304
	 * @param bool $forceRefresh whether to refresh the cache
305
	 * @param bool $all whether to return apps for all users, not only the
306
	 * currently logged in one
307
	 * @return string[]
308
	 */
309
	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...
310
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
311
			return array();
312
		}
313
		// in incognito mode or when logged out, $user will be false,
314
		// which is also the case during an upgrade
315
		$appManager = \OC::$server->getAppManager();
316
		if ($all) {
317
			$user = null;
318
		} else {
319
			$user = \OC::$server->getUserSession()->getUser();
320
		}
321
322
		if (is_null($user)) {
323
			$apps = $appManager->getInstalledApps();
324
		} else {
325
			$apps = $appManager->getEnabledAppsForUser($user);
326
		}
327
		$apps = array_filter($apps, function ($app) {
328
			return $app !== 'files';//we add this manually
329
		});
330
		sort($apps);
331
		array_unshift($apps, 'files');
332
		return $apps;
333
	}
334
335
	/**
336
	 * checks whether or not an app is enabled
337
	 *
338
	 * @param string $app app
339
	 * @return bool
340
	 *
341
	 * This function checks whether or not an app is enabled.
342
	 */
343
	public static function isEnabled($app) {
344
		return \OC::$server->getAppManager()->isEnabledForUser($app);
345
	}
346
347
	/**
348
	 * enables an app
349
	 *
350
	 * @param string $appId
351
	 * @param array $groups (optional) when set, only these groups will have access to the app
352
	 * @throws \Exception
353
	 * @return void
354
	 *
355
	 * This function set an app as enabled in appconfig.
356
	 */
357
	public function enable($appId,
358
						   $groups = null) {
359
		self::$enabledAppsCache = []; // flush
360
		$l = \OC::$server->getL10N('core');
361
		$config = \OC::$server->getConfig();
362
363
		// Check if app is already downloaded
364
		$installer = new Installer(
365
			\OC::$server->getAppFetcher(),
366
			\OC::$server->getHTTPClientService(),
367
			\OC::$server->getTempManager(),
368
			\OC::$server->getLogger()
369
		);
370
		$isDownloaded = $installer->isDownloaded($appId);
371
372
		if(!$isDownloaded) {
373
			$installer->downloadApp($appId);
374
		}
375
376
		if (!Installer::isInstalled($appId)) {
377
			$appId = self::installApp(
378
				$appId,
379
				$config,
380
				$l
381
			);
382
			$appPath = self::getAppPath($appId);
383
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 382 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...
384
			$installer->installApp($appId);
385
		} else {
386
			// check for required dependencies
387
			$info = self::getAppInfo($appId);
388
			self::checkAppDependencies($config, $l, $info);
0 ignored issues
show
Bug introduced by
It seems like $info defined by self::getAppInfo($appId) on line 387 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...
389
			$appPath = self::getAppPath($appId);
390
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 389 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...
391
			$installer->installApp($appId);
392
		}
393
394
		$appManager = \OC::$server->getAppManager();
395
		if (!is_null($groups)) {
396
			$groupManager = \OC::$server->getGroupManager();
397
			$groupsList = [];
398
			foreach ($groups as $group) {
399
				$groupItem = $groupManager->get($group);
400
				if ($groupItem instanceof \OCP\IGroup) {
401
					$groupsList[] = $groupManager->get($group);
402
				}
403
			}
404
			$appManager->enableAppForGroups($appId, $groupsList);
405
		} else {
406
			$appManager->enableApp($appId);
407
		}
408
409
		$info = self::getAppInfo($appId);
410 View Code Duplication
		if(isset($info['settings']) && is_array($info['settings'])) {
411
			$appPath = self::getAppPath($appId);
412
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 411 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...
413
			\OC::$server->getSettingsManager()->setupSettings($info['settings']);
414
		}
415
	}
416
417
	/**
418
	 * @param string $app
419
	 * @return bool
420
	 */
421
	public static function removeApp($app) {
422
		if (self::isShipped($app)) {
423
			return false;
424
		}
425
426
		$installer = new Installer(
427
			\OC::$server->getAppFetcher(),
428
			\OC::$server->getHTTPClientService(),
429
			\OC::$server->getTempManager(),
430
			\OC::$server->getLogger()
431
		);
432
		return $installer->removeApp($app);
433
	}
434
435
	/**
436
	 * This function set an app as disabled in appconfig.
437
	 *
438
	 * @param string $app app
439
	 * @throws Exception
440
	 */
441
	public static function disable($app) {
442
		// flush
443
		self::$enabledAppsCache = array();
444
445
		// run uninstall steps
446
		$appData = OC_App::getAppInfo($app);
447
		if (!is_null($appData)) {
448
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
449
		}
450
451
		// emit disable hook - needed anymore ?
452
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
453
454
		// finally disable it
455
		$appManager = \OC::$server->getAppManager();
456
		$appManager->disableApp($app);
457
	}
458
459
	// This is private as well. It simply works, so don't ask for more details
460
	private static function proceedNavigation($list) {
461
		usort($list, function($a, $b) {
462
			if (isset($a['order']) && isset($b['order'])) {
463
				return ($a['order'] < $b['order']) ? -1 : 1;
464
			} else if (isset($a['order']) || isset($b['order'])) {
465
				return isset($a['order']) ? -1 : 1;
466
			} else {
467
				return ($a['name'] < $b['name']) ? -1 : 1;
468
			}
469
		});
470
471
		$activeApp = OC::$server->getNavigationManager()->getActiveEntry();
472
		foreach ($list as $index => &$navEntry) {
473
			if ($navEntry['id'] == $activeApp) {
474
				$navEntry['active'] = true;
475
			} else {
476
				$navEntry['active'] = false;
477
			}
478
		}
479
		unset($navEntry);
480
481
		return $list;
482
	}
483
484
	/**
485
	 * Get the path where to install apps
486
	 *
487
	 * @return string|false
488
	 */
489
	public static function getInstallPath() {
490
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
491
			return false;
492
		}
493
494
		foreach (OC::$APPSROOTS as $dir) {
495
			if (isset($dir['writable']) && $dir['writable'] === true) {
496
				return $dir['path'];
497
			}
498
		}
499
500
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
501
		return null;
502
	}
503
504
505
	/**
506
	 * search for an app in all app-directories
507
	 *
508
	 * @param string $appId
509
	 * @return false|string
510
	 */
511
	public static function findAppInDirectories($appId) {
512
		$sanitizedAppId = self::cleanAppId($appId);
513
		if($sanitizedAppId !== $appId) {
514
			return false;
515
		}
516
		static $app_dir = array();
517
518
		if (isset($app_dir[$appId])) {
519
			return $app_dir[$appId];
520
		}
521
522
		$possibleApps = array();
523
		foreach (OC::$APPSROOTS as $dir) {
524
			if (file_exists($dir['path'] . '/' . $appId)) {
525
				$possibleApps[] = $dir;
526
			}
527
		}
528
529
		if (empty($possibleApps)) {
530
			return false;
531
		} elseif (count($possibleApps) === 1) {
532
			$dir = array_shift($possibleApps);
533
			$app_dir[$appId] = $dir;
534
			return $dir;
535
		} else {
536
			$versionToLoad = array();
537
			foreach ($possibleApps as $possibleApp) {
538
				$version = self::getAppVersionByPath($possibleApp['path']);
539
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
540
					$versionToLoad = array(
541
						'dir' => $possibleApp,
542
						'version' => $version,
543
					);
544
				}
545
			}
546
			$app_dir[$appId] = $versionToLoad['dir'];
547
			return $versionToLoad['dir'];
548
			//TODO - write test
549
		}
550
	}
551
552
	/**
553
	 * Get the directory for the given app.
554
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
555
	 *
556
	 * @param string $appId
557
	 * @return string|false
558
	 */
559
	public static function getAppPath($appId) {
560
		if ($appId === null || trim($appId) === '') {
561
			return false;
562
		}
563
564
		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...
565
			return $dir['path'] . '/' . $appId;
566
		}
567
		return false;
568
	}
569
570
	/**
571
	 * Get the path for the given app on the access
572
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
573
	 *
574
	 * @param string $appId
575
	 * @return string|false
576
	 */
577
	public static function getAppWebPath($appId) {
578
		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...
579
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
580
		}
581
		return false;
582
	}
583
584
	/**
585
	 * get the last version of the app from appinfo/info.xml
586
	 *
587
	 * @param string $appId
588
	 * @param bool $useCache
589
	 * @return string
590
	 */
591
	public static function getAppVersion($appId, $useCache = true) {
592
		if($useCache && isset(self::$appVersion[$appId])) {
593
			return self::$appVersion[$appId];
594
		}
595
596
		$file = self::getAppPath($appId);
597
		self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
598
		return self::$appVersion[$appId];
599
	}
600
601
	/**
602
	 * get app's version based on it's path
603
	 *
604
	 * @param string $path
605
	 * @return string
606
	 */
607
	public static function getAppVersionByPath($path) {
608
		$infoFile = $path . '/appinfo/info.xml';
609
		$appData = self::getAppInfo($infoFile, true);
610
		return isset($appData['version']) ? $appData['version'] : '';
611
	}
612
613
614
	/**
615
	 * Read all app metadata from the info.xml file
616
	 *
617
	 * @param string $appId id of the app or the path of the info.xml file
618
	 * @param bool $path
619
	 * @param string $lang
620
	 * @return array|null
621
	 * @note all data is read from info.xml, not just pre-defined fields
622
	 */
623
	public static function getAppInfo($appId, $path = false, $lang = null) {
624
		if ($path) {
625
			$file = $appId;
626
		} else {
627
			if ($lang === null && isset(self::$appInfo[$appId])) {
628
				return self::$appInfo[$appId];
629
			}
630
			$appPath = self::getAppPath($appId);
631
			if($appPath === false) {
632
				return null;
633
			}
634
			$file = $appPath . '/appinfo/info.xml';
635
		}
636
637
		$parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo'));
638
		$data = $parser->parse($file);
639
640
		if (is_array($data)) {
641
			$data = OC_App::parseAppInfo($data, $lang);
642
		}
643
		if(isset($data['ocsid'])) {
644
			$storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid');
645
			if($storedId !== '' && $storedId !== $data['ocsid']) {
646
				$data['ocsid'] = $storedId;
647
			}
648
		}
649
650
		if ($lang === null) {
651
			self::$appInfo[$appId] = $data;
652
		}
653
654
		return $data;
655
	}
656
657
	/**
658
	 * Returns the navigation
659
	 *
660
	 * @return array
661
	 *
662
	 * This function returns an array containing all entries added. The
663
	 * entries are sorted by the key 'order' ascending. Additional to the keys
664
	 * given for each app the following keys exist:
665
	 *   - active: boolean, signals if the user is on this navigation entry
666
	 */
667
	public static function getNavigation() {
668
		$entries = OC::$server->getNavigationManager()->getAll();
669
		return self::proceedNavigation($entries);
670
	}
671
672
	/**
673
	 * Returns the Settings Navigation
674
	 *
675
	 * @return string[]
676
	 *
677
	 * This function returns an array containing all settings pages added. The
678
	 * entries are sorted by the key 'order' ascending.
679
	 */
680
	public static function getSettingsNavigation() {
681
		$entries = OC::$server->getNavigationManager()->getAll('settings');
682
		return self::proceedNavigation($entries);
683
	}
684
685
	/**
686
	 * get the id of loaded app
687
	 *
688
	 * @return string
689
	 */
690
	public static function getCurrentApp() {
691
		$request = \OC::$server->getRequest();
692
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
693
		$topFolder = substr($script, 0, strpos($script, '/'));
694
		if (empty($topFolder)) {
695
			$path_info = $request->getPathInfo();
696
			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...
697
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
698
			}
699
		}
700
		if ($topFolder == 'apps') {
701
			$length = strlen($topFolder);
702
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
703
		} else {
704
			return $topFolder;
705
		}
706
	}
707
708
	/**
709
	 * @param string $type
710
	 * @return array
711
	 */
712
	public static function getForms($type) {
713
		$forms = array();
714
		switch ($type) {
715
			case 'admin':
716
				$source = self::$adminForms;
717
				break;
718
			case 'personal':
719
				$source = self::$personalForms;
720
				break;
721
			default:
722
				return array();
723
		}
724
		foreach ($source as $form) {
725
			$forms[] = include $form;
726
		}
727
		return $forms;
728
	}
729
730
	/**
731
	 * register an admin form to be shown
732
	 *
733
	 * @param string $app
734
	 * @param string $page
735
	 */
736
	public static function registerAdmin($app, $page) {
737
		self::$adminForms[] = $app . '/' . $page . '.php';
738
	}
739
740
	/**
741
	 * register a personal form to be shown
742
	 * @param string $app
743
	 * @param string $page
744
	 */
745
	public static function registerPersonal($app, $page) {
746
		self::$personalForms[] = $app . '/' . $page . '.php';
747
	}
748
749
	/**
750
	 * @param array $entry
751
	 */
752
	public static function registerLogIn(array $entry) {
753
		self::$altLogin[] = $entry;
754
	}
755
756
	/**
757
	 * @return array
758
	 */
759
	public static function getAlternativeLogIns() {
760
		return self::$altLogin;
761
	}
762
763
	/**
764
	 * get a list of all apps in the apps folder
765
	 *
766
	 * @return array an array of app names (string IDs)
767
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
768
	 */
769
	public static function getAllApps() {
770
771
		$apps = array();
772
773
		foreach (OC::$APPSROOTS as $apps_dir) {
774
			if (!is_readable($apps_dir['path'])) {
775
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
776
				continue;
777
			}
778
			$dh = opendir($apps_dir['path']);
779
780
			if (is_resource($dh)) {
781
				while (($file = readdir($dh)) !== false) {
782
783
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
784
785
						$apps[] = $file;
786
					}
787
				}
788
			}
789
		}
790
791
		return $apps;
792
	}
793
794
	/**
795
	 * List all apps, this is used in apps.php
796
	 *
797
	 * @return array
798
	 */
799
	public function listAllApps() {
800
		$installedApps = OC_App::getAllApps();
801
802
		//we don't want to show configuration for these
803
		$blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps();
804
		$appList = array();
805
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
806
		$urlGenerator = \OC::$server->getURLGenerator();
807
808
		foreach ($installedApps as $app) {
809
			if (array_search($app, $blacklist) === false) {
810
811
				$info = OC_App::getAppInfo($app, false, $langCode);
812
				if (!is_array($info)) {
813
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
814
					continue;
815
				}
816
817
				if (!isset($info['name'])) {
818
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
819
					continue;
820
				}
821
822
				$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...
823
				$info['groups'] = null;
824
				if ($enabled === 'yes') {
825
					$active = true;
826
				} else if ($enabled === 'no') {
827
					$active = false;
828
				} else {
829
					$active = true;
830
					$info['groups'] = $enabled;
831
				}
832
833
				$info['active'] = $active;
834
835
				if (self::isShipped($app)) {
836
					$info['internal'] = true;
837
					$info['level'] = self::officialApp;
838
					$info['removable'] = false;
839
				} else {
840
					$info['internal'] = false;
841
					$info['removable'] = true;
842
				}
843
844
				$appPath = self::getAppPath($app);
845
				if($appPath !== false) {
846
					$appIcon = $appPath . '/img/' . $app . '.svg';
847
					if (file_exists($appIcon)) {
848
						$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, $app . '.svg');
849
						$info['previewAsIcon'] = true;
850
					} else {
851
						$appIcon = $appPath . '/img/app.svg';
852
						if (file_exists($appIcon)) {
853
							$info['preview'] = \OC::$server->getURLGenerator()->imagePath($app, 'app.svg');
854
							$info['previewAsIcon'] = true;
855
						}
856
					}
857
				}
858
				// fix documentation
859
				if (isset($info['documentation']) && is_array($info['documentation'])) {
860
					foreach ($info['documentation'] as $key => $url) {
861
						// If it is not an absolute URL we assume it is a key
862
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
863
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
864
							$url = $urlGenerator->linkToDocs($url);
865
						}
866
867
						$info['documentation'][$key] = $url;
868
					}
869
				}
870
871
				$info['version'] = OC_App::getAppVersion($app);
872
				$appList[] = $info;
873
			}
874
		}
875
876
		return $appList;
877
	}
878
879
	/**
880
	 * Returns the internal app ID or false
881
	 * @param string $ocsID
882
	 * @return string|false
883
	 */
884
	public static function getInternalAppIdByOcs($ocsID) {
885
		if(is_numeric($ocsID)) {
886
			$idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
887
			if(array_search($ocsID, $idArray)) {
888
				return array_search($ocsID, $idArray);
889
			}
890
		}
891
		return false;
892
	}
893
894
	public static function shouldUpgrade($app) {
895
		$versions = self::getAppVersions();
896
		$currentVersion = OC_App::getAppVersion($app);
897
		if ($currentVersion && isset($versions[$app])) {
898
			$installedVersion = $versions[$app];
899
			if (!version_compare($currentVersion, $installedVersion, '=')) {
900
				return true;
901
			}
902
		}
903
		return false;
904
	}
905
906
	/**
907
	 * Adjust the number of version parts of $version1 to match
908
	 * the number of version parts of $version2.
909
	 *
910
	 * @param string $version1 version to adjust
911
	 * @param string $version2 version to take the number of parts from
912
	 * @return string shortened $version1
913
	 */
914
	private static function adjustVersionParts($version1, $version2) {
915
		$version1 = explode('.', $version1);
916
		$version2 = explode('.', $version2);
917
		// reduce $version1 to match the number of parts in $version2
918
		while (count($version1) > count($version2)) {
919
			array_pop($version1);
920
		}
921
		// if $version1 does not have enough parts, add some
922
		while (count($version1) < count($version2)) {
923
			$version1[] = '0';
924
		}
925
		return implode('.', $version1);
926
	}
927
928
	/**
929
	 * Check whether the current ownCloud version matches the given
930
	 * application's version requirements.
931
	 *
932
	 * The comparison is made based on the number of parts that the
933
	 * app info version has. For example for ownCloud 6.0.3 if the
934
	 * app info version is expecting version 6.0, the comparison is
935
	 * made on the first two parts of the ownCloud version.
936
	 * This means that it's possible to specify "requiremin" => 6
937
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
938
	 *
939
	 * @param string $ocVersion ownCloud version to check against
940
	 * @param array $appInfo app info (from xml)
941
	 *
942
	 * @return boolean true if compatible, otherwise false
943
	 */
944
	public static function isAppCompatible($ocVersion, $appInfo) {
945
		$requireMin = '';
946
		$requireMax = '';
947
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
948
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
949 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
950
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
951
		} else if (isset($appInfo['requiremin'])) {
952
			$requireMin = $appInfo['requiremin'];
953
		} else if (isset($appInfo['require'])) {
954
			$requireMin = $appInfo['require'];
955
		}
956
957
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
958
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
959 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
960
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
961
		} else if (isset($appInfo['requiremax'])) {
962
			$requireMax = $appInfo['requiremax'];
963
		}
964
965
		if (is_array($ocVersion)) {
966
			$ocVersion = implode('.', $ocVersion);
967
		}
968
969 View Code Duplication
		if (!empty($requireMin)
970
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
971
		) {
972
973
			return false;
974
		}
975
976 View Code Duplication
		if (!empty($requireMax)
977
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
978
		) {
979
			return false;
980
		}
981
982
		return true;
983
	}
984
985
	/**
986
	 * get the installed version of all apps
987
	 */
988
	public static function getAppVersions() {
989
		static $versions;
990
991
		if(!$versions) {
992
			$appConfig = \OC::$server->getAppConfig();
993
			$versions = $appConfig->getValues(false, 'installed_version');
994
		}
995
		return $versions;
996
	}
997
998
	/**
999
	 * @param string $app
1000
	 * @param \OCP\IConfig $config
1001
	 * @param \OCP\IL10N $l
1002
	 * @return bool
1003
	 *
1004
	 * @throws Exception if app is not compatible with this version of ownCloud
1005
	 * @throws Exception if no app-name was specified
1006
	 */
1007
	public function installApp($app,
1008
							   \OCP\IConfig $config,
1009
							   \OCP\IL10N $l) {
1010
		if ($app !== false) {
1011
			// check if the app is compatible with this version of ownCloud
1012
			$info = self::getAppInfo($app);
1013
			if(!is_array($info)) {
1014
				throw new \Exception(
1015
					$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
1016
						[$info['name']]
1017
					)
1018
				);
1019
			}
1020
1021
			$version = \OCP\Util::getVersion();
1022
			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...
1023
				throw new \Exception(
1024
					$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
1025
						array($info['name'])
1026
					)
1027
				);
1028
			}
1029
1030
			// check for required dependencies
1031
			self::checkAppDependencies($config, $l, $info);
1032
1033
			$config->setAppValue($app, 'enabled', 'yes');
1034
			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...
1035
				$config->setAppValue($app, 'ocsid', $appData['id']);
1036
			}
1037
1038 View Code Duplication
			if(isset($info['settings']) && is_array($info['settings'])) {
1039
				$appPath = self::getAppPath($app);
1040
				self::registerAutoloading($app, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($app) on line 1039 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...
1041
				\OC::$server->getSettingsManager()->setupSettings($info['settings']);
1042
			}
1043
1044
			\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
1045
		} else {
1046
			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...
1047
				throw new \Exception($l->t("No app name specified"));
1048
			} else {
1049
				throw new \Exception($l->t("App '%s' could not be installed!", $appName));
1050
			}
1051
		}
1052
1053
		return $app;
1054
	}
1055
1056
	/**
1057
	 * update the database for the app and call the update script
1058
	 *
1059
	 * @param string $appId
1060
	 * @return bool
1061
	 */
1062
	public static function updateApp($appId) {
1063
		$appPath = self::getAppPath($appId);
1064
		if($appPath === false) {
1065
			return false;
1066
		}
1067
		$appData = self::getAppInfo($appId);
1068
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
1069
		if (file_exists($appPath . '/appinfo/database.xml')) {
1070
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
1071
		}
1072
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
1073
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
1074
		unset(self::$appVersion[$appId]);
1075
		// run upgrade code
1076
		if (file_exists($appPath . '/appinfo/update.php')) {
1077
			self::loadApp($appId);
1078
			include $appPath . '/appinfo/update.php';
1079
		}
1080
		self::setupBackgroundJobs($appData['background-jobs']);
1081 View Code Duplication
		if(isset($appData['settings']) && is_array($appData['settings'])) {
1082
			$appPath = self::getAppPath($appId);
1083
			self::registerAutoloading($appId, $appPath);
0 ignored issues
show
Security Bug introduced by
It seems like $appPath defined by self::getAppPath($appId) on line 1082 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...
1084
			\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
1085
		}
1086
1087
		//set remote/public handlers
1088
		if (array_key_exists('ocsid', $appData)) {
1089
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
1090 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
1091
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
1092
		}
1093
		foreach ($appData['remote'] as $name => $path) {
1094
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
1095
		}
1096 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
1097
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
1098
		}
1099
1100
		self::setAppTypes($appId);
1101
1102
		$version = \OC_App::getAppVersion($appId);
1103
		\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...
1104
1105
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
1106
			ManagerEvent::EVENT_APP_UPDATE, $appId
1107
		));
1108
1109
		return true;
1110
	}
1111
1112
	/**
1113
	 * @param string $appId
1114
	 * @param string[] $steps
1115
	 * @throws \OC\NeedsUpdateException
1116
	 */
1117
	public static function executeRepairSteps($appId, array $steps) {
1118
		if (empty($steps)) {
1119
			return;
1120
		}
1121
		// load the app
1122
		self::loadApp($appId);
1123
1124
		$dispatcher = OC::$server->getEventDispatcher();
1125
1126
		// load the steps
1127
		$r = new Repair([], $dispatcher);
1128
		foreach ($steps as $step) {
1129
			try {
1130
				$r->addStep($step);
1131
			} catch (Exception $ex) {
1132
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
1133
				\OC::$server->getLogger()->logException($ex);
1134
			}
1135
		}
1136
		// run the steps
1137
		$r->run();
1138
	}
1139
1140
	public static function setupBackgroundJobs(array $jobs) {
1141
		$queue = \OC::$server->getJobList();
1142
		foreach ($jobs as $job) {
1143
			$queue->add($job);
1144
		}
1145
	}
1146
1147
	/**
1148
	 * @param string $appId
1149
	 * @param string[] $steps
1150
	 */
1151
	private static function setupLiveMigrations($appId, array $steps) {
1152
		$queue = \OC::$server->getJobList();
1153
		foreach ($steps as $step) {
1154
			$queue->add('OC\Migration\BackgroundRepair', [
1155
				'app' => $appId,
1156
				'step' => $step]);
1157
		}
1158
	}
1159
1160
	/**
1161
	 * @param string $appId
1162
	 * @return \OC\Files\View|false
1163
	 */
1164
	public static function getStorage($appId) {
1165
		if (OC_App::isEnabled($appId)) { //sanity check
1166
			if (\OC::$server->getUserSession()->isLoggedIn()) {
1167
				$view = new \OC\Files\View('/' . OC_User::getUser());
1168
				if (!$view->file_exists($appId)) {
1169
					$view->mkdir($appId);
1170
				}
1171
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1172
			} else {
1173
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1174
				return false;
1175
			}
1176
		} else {
1177
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1178
			return false;
1179
		}
1180
	}
1181
1182
	protected static function findBestL10NOption($options, $lang) {
1183
		$fallback = $similarLangFallback = $englishFallback = false;
1184
1185
		$lang = strtolower($lang);
1186
		$similarLang = $lang;
1187
		if (strpos($similarLang, '_')) {
1188
			// For "de_DE" we want to find "de" and the other way around
1189
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1190
		}
1191
1192
		foreach ($options as $option) {
1193
			if (is_array($option)) {
1194
				if ($fallback === false) {
1195
					$fallback = $option['@value'];
1196
				}
1197
1198
				if (!isset($option['@attributes']['lang'])) {
1199
					continue;
1200
				}
1201
1202
				$attributeLang = strtolower($option['@attributes']['lang']);
1203
				if ($attributeLang === $lang) {
1204
					return $option['@value'];
1205
				}
1206
1207
				if ($attributeLang === $similarLang) {
1208
					$similarLangFallback = $option['@value'];
1209
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1210
					if ($similarLangFallback === false) {
1211
						$similarLangFallback =  $option['@value'];
1212
					}
1213
				}
1214
			} else {
1215
				$englishFallback = $option;
1216
			}
1217
		}
1218
1219
		if ($similarLangFallback !== false) {
1220
			return $similarLangFallback;
1221
		} else if ($englishFallback !== false) {
1222
			return $englishFallback;
1223
		}
1224
		return (string) $fallback;
1225
	}
1226
1227
	/**
1228
	 * parses the app data array and enhanced the 'description' value
1229
	 *
1230
	 * @param array $data the app data
1231
	 * @param string $lang
1232
	 * @return array improved app data
1233
	 */
1234
	public static function parseAppInfo(array $data, $lang = null) {
1235
1236 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...
1237
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1238
		}
1239 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...
1240
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1241
		}
1242
		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...
1243
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1244
		} else if (isset($data['description']) && is_string($data['description'])) {
1245
			$data['description'] = trim($data['description']);
1246
		} else  {
1247
			$data['description'] = '';
1248
		}
1249
1250
		return $data;
1251
	}
1252
1253
	/**
1254
	 * @param \OCP\IConfig $config
1255
	 * @param \OCP\IL10N $l
1256
	 * @param array $info
1257
	 * @throws \Exception
1258
	 */
1259
	protected static function checkAppDependencies($config, $l, $info) {
1260
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1261
		$missing = $dependencyAnalyzer->analyze($info);
1262
		if (!empty($missing)) {
1263
			$missingMsg = join(PHP_EOL, $missing);
1264
			throw new \Exception(
1265
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1266
					[$info['name'], $missingMsg]
1267
				)
1268
			);
1269
		}
1270
	}
1271
}
1272