Completed
Push — master ( 8a505e...14bc9b )
by Morris
51:24 queued 31:58
created

OC_App::getEnabledApps()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 5
nop 2
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
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 Joas Schilling <[email protected]>
18
 * @author Julius Haertl <[email protected]>
19
 * @author Julius Härtl <[email protected]>
20
 * @author Jörn Friedrich Dreyer <[email protected]>
21
 * @author Kamil Domanski <[email protected]>
22
 * @author Klaas Freitag <[email protected]>
23
 * @author Lukas Reschke <[email protected]>
24
 * @author Markus Goetz <[email protected]>
25
 * @author Morris Jobke <[email protected]>
26
 * @author RealRancor <[email protected]>
27
 * @author Robin Appelman <[email protected]>
28
 * @author Robin McCorkell <[email protected]>
29
 * @author Roeland Jago Douma <[email protected]>
30
 * @author Sam Tuke <[email protected]>
31
 * @author Sebastian Wessalowski <[email protected]>
32
 * @author Thomas Müller <[email protected]>
33
 * @author Thomas Tanghus <[email protected]>
34
 * @author Tom Needham <[email protected]>
35
 * @author Vincent Petry <[email protected]>
36
 *
37
 * @license AGPL-3.0
38
 *
39
 * This code is free software: you can redistribute it and/or modify
40
 * it under the terms of the GNU Affero General Public License, version 3,
41
 * as published by the Free Software Foundation.
42
 *
43
 * This program is distributed in the hope that it will be useful,
44
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
45
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46
 * GNU Affero General Public License for more details.
47
 *
48
 * You should have received a copy of the GNU Affero General Public License, version 3,
49
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
50
 *
51
 */
52
use OC\App\DependencyAnalyzer;
53
use OC\App\Platform;
54
use OC\DB\MigrationService;
55
use OC\Installer;
56
use OC\Repair;
57
use OCP\App\ManagerEvent;
58
59
/**
60
 * This class manages the apps. It allows them to register and integrate in the
61
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
62
 * upgrading and removing apps.
63
 */
64
class OC_App {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
65
	static private $adminForms = array();
66
	static private $personalForms = 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)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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
	 * @throws Exception
137
	 */
138
	public static function loadApp($app) {
139
		self::$loadedApps[] = $app;
140
		$appPath = self::getAppPath($app);
141
		if($appPath === false) {
142
			return;
143
		}
144
145
		// in case someone calls loadApp() directly
146
		self::registerAutoloading($app, $appPath);
147
148
		if (is_file($appPath . '/appinfo/app.php')) {
149
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
150
			try {
151
				self::requireAppFile($app);
152
			} 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...
153
				\OC::$server->getLogger()->logException($ex);
154
				if (!\OC::$server->getAppManager()->isShipped($app)) {
155
					// Only disable apps which are not shipped
156
					self::disable($app);
157
				}
158
			}
159
			if (self::isType($app, array('authentication'))) {
160
				// since authentication apps affect the "is app enabled for group" check,
161
				// the enabled apps cache needs to be cleared to make sure that the
162
				// next time getEnableApps() is called it will also include apps that were
163
				// enabled for groups
164
				self::$enabledAppsCache = array();
165
			}
166
			\OC::$server->getEventLogger()->end('load_app_' . $app);
167
		}
168
169
		$info = self::getAppInfo($app);
170 View Code Duplication
		if (!empty($info['activity']['filters'])) {
171
			foreach ($info['activity']['filters'] as $filter) {
172
				\OC::$server->getActivityManager()->registerFilter($filter);
173
			}
174
		}
175 View Code Duplication
		if (!empty($info['activity']['settings'])) {
176
			foreach ($info['activity']['settings'] as $setting) {
177
				\OC::$server->getActivityManager()->registerSetting($setting);
178
			}
179
		}
180 View Code Duplication
		if (!empty($info['activity']['providers'])) {
181
			foreach ($info['activity']['providers'] as $provider) {
182
				\OC::$server->getActivityManager()->registerProvider($provider);
183
			}
184
		}
185
186 View Code Duplication
		if (!empty($info['settings']['admin'])) {
187
			foreach ($info['settings']['admin'] as $setting) {
188
				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
189
			}
190
		}
191
		if (!empty($info['settings']['admin-section'])) {
192
			foreach ($info['settings']['admin-section'] as $section) {
193
				\OC::$server->getSettingsManager()->registerSection('admin', $section);
194
			}
195
		}
196
		if (!empty($info['settings']['personal'])) {
197
			foreach ($info['settings']['personal'] as $setting) {
198
				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
199
			}
200
		}
201
		if (!empty($info['settings']['personal-section'])) {
202
			foreach ($info['settings']['personal-section'] as $section) {
203
				\OC::$server->getSettingsManager()->registerSection('personal', $section);
204
			}
205
		}
206
207
		if (!empty($info['collaboration']['plugins'])) {
208
			// deal with one or many plugin entries
209
			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
210
				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
211
			foreach ($plugins as $plugin) {
212
				if($plugin['@attributes']['type'] === 'collaborator-search') {
213
					$pluginInfo = [
214
						'shareType' => $plugin['@attributes']['share-type'],
215
						'class' => $plugin['@value'],
216
					];
217
					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
218
				} else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
219
					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
220
				}
221
			}
222
		}
223
	}
224
225
	/**
226
	 * @internal
227
	 * @param string $app
228
	 * @param string $path
229
	 */
230
	public static function registerAutoloading($app, $path) {
231
		$key = $app . '-' . $path;
232
		if(isset(self::$alreadyRegistered[$key])) {
233
			return;
234
		}
235
236
		self::$alreadyRegistered[$key] = true;
237
238
		// Register on PSR-4 composer autoloader
239
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
240
		\OC::$server->registerNamespace($app, $appNamespace);
241
242
		if (file_exists($path . '/composer/autoload.php')) {
243
			require_once $path . '/composer/autoload.php';
244
		} else {
245
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
246
			// Register on legacy autoloader
247
			\OC::$loader->addValidRoot($path);
248
		}
249
250
		// Register Test namespace only when testing
251
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
252
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
253
		}
254
	}
255
256
	/**
257
	 * Load app.php from the given app
258
	 *
259
	 * @param string $app app name
260
	 * @throws Error
261
	 */
262
	private static function requireAppFile($app) {
263
		// encapsulated here to avoid variable scope conflicts
264
		require_once $app . '/appinfo/app.php';
265
	}
266
267
	/**
268
	 * check if an app is of a specific type
269
	 *
270
	 * @param string $app
271
	 * @param string|array $types
272
	 * @return bool
273
	 */
274
	public static function isType($app, $types) {
275
		if (is_string($types)) {
276
			$types = array($types);
277
		}
278
		$appTypes = self::getAppTypes($app);
279
		foreach ($types as $type) {
280
			if (array_search($type, $appTypes) !== false) {
281
				return true;
282
			}
283
		}
284
		return false;
285
	}
286
287
	/**
288
	 * get the types of an app
289
	 *
290
	 * @param string $app
291
	 * @return array
292
	 */
293
	private static function getAppTypes($app) {
294
		//load the cache
295
		if (count(self::$appTypes) == 0) {
296
			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...
297
		}
298
299
		if (isset(self::$appTypes[$app])) {
300
			return explode(',', self::$appTypes[$app]);
301
		} else {
302
			return array();
303
		}
304
	}
305
306
	/**
307
	 * read app types from info.xml and cache them in the database
308
	 */
309
	public static function setAppTypes($app) {
310
		$appData = self::getAppInfo($app);
311
		if(!is_array($appData)) {
312
			return;
313
		}
314
315
		if (isset($appData['types'])) {
316
			$appTypes = implode(',', $appData['types']);
317
		} else {
318
			$appTypes = '';
319
			$appData['types'] = [];
320
		}
321
322
		\OC::$server->getConfig()->setAppValue($app, 'types', $appTypes);
323
324
		if (\OC::$server->getAppManager()->hasProtectedAppType($appData['types'])) {
325
			$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'yes');
326
			if ($enabled !== 'yes' && $enabled !== 'no') {
327
				\OC::$server->getConfig()->setAppValue($app, 'enabled', 'yes');
328
			}
329
		}
330
	}
331
332
	/**
333
	 * get all enabled apps
334
	 */
335
	protected static $enabledAppsCache = array();
336
337
	/**
338
	 * Returns apps enabled for the current user.
339
	 *
340
	 * @param bool $forceRefresh whether to refresh the cache
341
	 * @param bool $all whether to return apps for all users, not only the
342
	 * currently logged in one
343
	 * @return string[]
344
	 */
345
	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...
346
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
347
			return array();
348
		}
349
		// in incognito mode or when logged out, $user will be false,
350
		// which is also the case during an upgrade
351
		$appManager = \OC::$server->getAppManager();
352
		if ($all) {
353
			$user = null;
354
		} else {
355
			$user = \OC::$server->getUserSession()->getUser();
356
		}
357
358
		if (is_null($user)) {
359
			$apps = $appManager->getInstalledApps();
360
		} else {
361
			$apps = $appManager->getEnabledAppsForUser($user);
362
		}
363
		$apps = array_filter($apps, function ($app) {
364
			return $app !== 'files';//we add this manually
365
		});
366
		sort($apps);
367
		array_unshift($apps, 'files');
368
		return $apps;
369
	}
370
371
	/**
372
	 * checks whether or not an app is enabled
373
	 *
374
	 * @param string $app app
375
	 * @return bool
376
	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
377
	 *
378
	 * This function checks whether or not an app is enabled.
379
	 */
380
	public static function isEnabled($app) {
381
		return \OC::$server->getAppManager()->isEnabledForUser($app);
382
	}
383
384
	/**
385
	 * enables an app
386
	 *
387
	 * @param string $appId
388
	 * @param array $groups (optional) when set, only these groups will have access to the app
389
	 * @throws \Exception
390
	 * @return void
391
	 *
392
	 * This function set an app as enabled in appconfig.
393
	 */
394
	public function enable($appId,
395
						   $groups = null) {
396
		self::$enabledAppsCache = []; // flush
397
398
		// Check if app is already downloaded
399
		$installer = \OC::$server->query(Installer::class);
400
		$isDownloaded = $installer->isDownloaded($appId);
401
402
		if(!$isDownloaded) {
403
			$installer->downloadApp($appId);
404
		}
405
406
		$installer->installApp($appId);
407
408
		$appManager = \OC::$server->getAppManager();
409
		if (!is_null($groups)) {
410
			$groupManager = \OC::$server->getGroupManager();
411
			$groupsList = [];
412
			foreach ($groups as $group) {
413
				$groupItem = $groupManager->get($group);
414
				if ($groupItem instanceof \OCP\IGroup) {
415
					$groupsList[] = $groupManager->get($group);
416
				}
417
			}
418
			$appManager->enableAppForGroups($appId, $groupsList);
419
		} else {
420
			$appManager->enableApp($appId);
421
		}
422
	}
423
424
	/**
425
	 * This function set an app as disabled in appconfig.
426
	 *
427
	 * @param string $app app
428
	 * @throws Exception
429
	 */
430
	public static function disable($app) {
431
		// flush
432
		self::$enabledAppsCache = array();
433
434
		// run uninstall steps
435
		$appData = OC_App::getAppInfo($app);
436
		if (!is_null($appData)) {
437
			OC_App::executeRepairSteps($app, $appData['repair-steps']['uninstall']);
438
		}
439
440
		// emit disable hook - needed anymore ?
441
		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
442
443
		// finally disable it
444
		$appManager = \OC::$server->getAppManager();
445
		$appManager->disableApp($app);
446
	}
447
448
	/**
449
	 * Get the path where to install apps
450
	 *
451
	 * @return string|false
452
	 */
453
	public static function getInstallPath() {
454
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
455
			return false;
456
		}
457
458
		foreach (OC::$APPSROOTS as $dir) {
459
			if (isset($dir['writable']) && $dir['writable'] === true) {
460
				return $dir['path'];
461
			}
462
		}
463
464
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', \OCP\Util::ERROR);
465
		return null;
466
	}
467
468
469
	/**
470
	 * search for an app in all app-directories
471
	 *
472
	 * @param string $appId
473
	 * @return false|string
474
	 */
475
	public static function findAppInDirectories($appId) {
476
		$sanitizedAppId = self::cleanAppId($appId);
477
		if($sanitizedAppId !== $appId) {
478
			return false;
479
		}
480
		static $app_dir = array();
481
482
		if (isset($app_dir[$appId])) {
483
			return $app_dir[$appId];
484
		}
485
486
		$possibleApps = array();
487
		foreach (OC::$APPSROOTS as $dir) {
488
			if (file_exists($dir['path'] . '/' . $appId)) {
489
				$possibleApps[] = $dir;
490
			}
491
		}
492
493
		if (empty($possibleApps)) {
494
			return false;
495
		} elseif (count($possibleApps) === 1) {
496
			$dir = array_shift($possibleApps);
497
			$app_dir[$appId] = $dir;
498
			return $dir;
499
		} else {
500
			$versionToLoad = array();
501
			foreach ($possibleApps as $possibleApp) {
502
				$version = self::getAppVersionByPath($possibleApp['path']);
503
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
504
					$versionToLoad = array(
505
						'dir' => $possibleApp,
506
						'version' => $version,
507
					);
508
				}
509
			}
510
			$app_dir[$appId] = $versionToLoad['dir'];
511
			return $versionToLoad['dir'];
512
			//TODO - write test
513
		}
514
	}
515
516
	/**
517
	 * Get the directory for the given app.
518
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
519
	 *
520
	 * @param string $appId
521
	 * @return string|false
522
	 */
523
	public static function getAppPath($appId) {
524
		if ($appId === null || trim($appId) === '') {
525
			return false;
526
		}
527
528
		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...
529
			return $dir['path'] . '/' . $appId;
530
		}
531
		return false;
532
	}
533
534
	/**
535
	 * Get the path for the given app on the access
536
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
537
	 *
538
	 * @param string $appId
539
	 * @return string|false
540
	 */
541
	public static function getAppWebPath($appId) {
542
		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...
543
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
544
		}
545
		return false;
546
	}
547
548
	/**
549
	 * get the last version of the app from appinfo/info.xml
550
	 *
551
	 * @param string $appId
552
	 * @param bool $useCache
553
	 * @return string
554
	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
555
	 */
556
	public static function getAppVersion($appId, $useCache = true) {
557
		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
558
	}
559
560
	/**
561
	 * get app's version based on it's path
562
	 *
563
	 * @param string $path
564
	 * @return string
565
	 */
566
	public static function getAppVersionByPath($path) {
567
		$infoFile = $path . '/appinfo/info.xml';
568
		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
569
		return isset($appData['version']) ? $appData['version'] : '';
570
	}
571
572
573
	/**
574
	 * Read all app metadata from the info.xml file
575
	 *
576
	 * @param string $appId id of the app or the path of the info.xml file
577
	 * @param bool $path
578
	 * @param string $lang
579
	 * @return array|null
580
	 * @note all data is read from info.xml, not just pre-defined fields
581
	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
582
	 */
583
	public static function getAppInfo($appId, $path = false, $lang = null) {
584
		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
585
	}
586
587
	/**
588
	 * Returns the navigation
589
	 *
590
	 * @return array
591
	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
592
	 *
593
	 * This function returns an array containing all entries added. The
594
	 * entries are sorted by the key 'order' ascending. Additional to the keys
595
	 * given for each app the following keys exist:
596
	 *   - active: boolean, signals if the user is on this navigation entry
597
	 */
598
	public static function getNavigation() {
599
		return OC::$server->getNavigationManager()->getAll();
600
	}
601
602
	/**
603
	 * Returns the Settings Navigation
604
	 *
605
	 * @return string[]
606
	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
607
	 *
608
	 * This function returns an array containing all settings pages added. The
609
	 * entries are sorted by the key 'order' ascending.
610
	 */
611
	public static function getSettingsNavigation() {
612
		return OC::$server->getNavigationManager()->getAll('settings');
613
	}
614
615
	/**
616
	 * get the id of loaded app
617
	 *
618
	 * @return string
619
	 */
620
	public static function getCurrentApp() {
621
		$request = \OC::$server->getRequest();
622
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
623
		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
624
		if (empty($topFolder)) {
625
			$path_info = $request->getPathInfo();
626
			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...
627
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
628
			}
629
		}
630
		if ($topFolder == 'apps') {
631
			$length = strlen($topFolder);
632
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
633
		} else {
634
			return $topFolder;
635
		}
636
	}
637
638
	/**
639
	 * @param string $type
640
	 * @return array
641
	 */
642
	public static function getForms($type) {
643
		$forms = array();
644
		switch ($type) {
645
			case 'admin':
646
				$source = self::$adminForms;
647
				break;
648
			case 'personal':
649
				$source = self::$personalForms;
650
				break;
651
			default:
652
				return array();
653
		}
654
		foreach ($source as $form) {
655
			$forms[] = include $form;
656
		}
657
		return $forms;
658
	}
659
660
	/**
661
	 * register an admin form to be shown
662
	 *
663
	 * @param string $app
664
	 * @param string $page
665
	 */
666
	public static function registerAdmin($app, $page) {
667
		self::$adminForms[] = $app . '/' . $page . '.php';
668
	}
669
670
	/**
671
	 * register a personal form to be shown
672
	 * @param string $app
673
	 * @param string $page
674
	 */
675
	public static function registerPersonal($app, $page) {
676
		self::$personalForms[] = $app . '/' . $page . '.php';
677
	}
678
679
	/**
680
	 * @param array $entry
681
	 */
682
	public static function registerLogIn(array $entry) {
683
		self::$altLogin[] = $entry;
684
	}
685
686
	/**
687
	 * @return array
688
	 */
689
	public static function getAlternativeLogIns() {
690
		return self::$altLogin;
691
	}
692
693
	/**
694
	 * get a list of all apps in the apps folder
695
	 *
696
	 * @return array an array of app names (string IDs)
697
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
698
	 */
699
	public static function getAllApps() {
700
701
		$apps = array();
702
703
		foreach (OC::$APPSROOTS as $apps_dir) {
704
			if (!is_readable($apps_dir['path'])) {
705
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], \OCP\Util::WARN);
706
				continue;
707
			}
708
			$dh = opendir($apps_dir['path']);
709
710
			if (is_resource($dh)) {
711
				while (($file = readdir($dh)) !== false) {
712
713
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
714
715
						$apps[] = $file;
716
					}
717
				}
718
			}
719
		}
720
721
		$apps = array_unique($apps);
722
723
		return $apps;
724
	}
725
726
	/**
727
	 * List all apps, this is used in apps.php
728
	 *
729
	 * @return array
730
	 */
731
	public function listAllApps() {
732
		$installedApps = OC_App::getAllApps();
733
734
		$appManager = \OC::$server->getAppManager();
735
		//we don't want to show configuration for these
736
		$blacklist = $appManager->getAlwaysEnabledApps();
737
		$appList = array();
738
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
739
		$urlGenerator = \OC::$server->getURLGenerator();
740
741
		foreach ($installedApps as $app) {
742
			if (array_search($app, $blacklist) === false) {
743
744
				$info = OC_App::getAppInfo($app, false, $langCode);
745
				if (!is_array($info)) {
746
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR);
747
					continue;
748
				}
749
750
				if (!isset($info['name'])) {
751
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', \OCP\Util::ERROR);
752
					continue;
753
				}
754
755
				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
756
				$info['groups'] = null;
757
				if ($enabled === 'yes') {
758
					$active = true;
759
				} else if ($enabled === 'no') {
760
					$active = false;
761
				} else {
762
					$active = true;
763
					$info['groups'] = $enabled;
764
				}
765
766
				$info['active'] = $active;
767
768
				if ($appManager->isShipped($app)) {
769
					$info['internal'] = true;
770
					$info['level'] = self::officialApp;
771
					$info['removable'] = false;
772
				} else {
773
					$info['internal'] = false;
774
					$info['removable'] = true;
775
				}
776
777
				$appPath = self::getAppPath($app);
778
				if($appPath !== false) {
779
					$appIcon = $appPath . '/img/' . $app . '.svg';
780
					if (file_exists($appIcon)) {
781
						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
782
						$info['previewAsIcon'] = true;
783
					} else {
784
						$appIcon = $appPath . '/img/app.svg';
785
						if (file_exists($appIcon)) {
786
							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
787
							$info['previewAsIcon'] = true;
788
						}
789
					}
790
				}
791
				// fix documentation
792
				if (isset($info['documentation']) && is_array($info['documentation'])) {
793
					foreach ($info['documentation'] as $key => $url) {
794
						// If it is not an absolute URL we assume it is a key
795
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
796
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
797
							$url = $urlGenerator->linkToDocs($url);
798
						}
799
800
						$info['documentation'][$key] = $url;
801
					}
802
				}
803
804
				$info['version'] = OC_App::getAppVersion($app);
805
				$appList[] = $info;
806
			}
807
		}
808
809
		return $appList;
810
	}
811
812
	public static function shouldUpgrade($app) {
813
		$versions = self::getAppVersions();
814
		$currentVersion = OC_App::getAppVersion($app);
815
		if ($currentVersion && isset($versions[$app])) {
816
			$installedVersion = $versions[$app];
817
			if (!version_compare($currentVersion, $installedVersion, '=')) {
818
				return true;
819
			}
820
		}
821
		return false;
822
	}
823
824
	/**
825
	 * Adjust the number of version parts of $version1 to match
826
	 * the number of version parts of $version2.
827
	 *
828
	 * @param string $version1 version to adjust
829
	 * @param string $version2 version to take the number of parts from
830
	 * @return string shortened $version1
831
	 */
832
	private static function adjustVersionParts($version1, $version2) {
833
		$version1 = explode('.', $version1);
834
		$version2 = explode('.', $version2);
835
		// reduce $version1 to match the number of parts in $version2
836
		while (count($version1) > count($version2)) {
837
			array_pop($version1);
838
		}
839
		// if $version1 does not have enough parts, add some
840
		while (count($version1) < count($version2)) {
841
			$version1[] = '0';
842
		}
843
		return implode('.', $version1);
844
	}
845
846
	/**
847
	 * Check whether the current ownCloud version matches the given
848
	 * application's version requirements.
849
	 *
850
	 * The comparison is made based on the number of parts that the
851
	 * app info version has. For example for ownCloud 6.0.3 if the
852
	 * app info version is expecting version 6.0, the comparison is
853
	 * made on the first two parts of the ownCloud version.
854
	 * This means that it's possible to specify "requiremin" => 6
855
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
856
	 *
857
	 * @param string $ocVersion ownCloud version to check against
858
	 * @param array $appInfo app info (from xml)
859
	 *
860
	 * @return boolean true if compatible, otherwise false
861
	 */
862
	public static function isAppCompatible($ocVersion, $appInfo) {
863
		$requireMin = '';
864
		$requireMax = '';
865
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
866
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
867 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
868
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
869
		} else if (isset($appInfo['requiremin'])) {
870
			$requireMin = $appInfo['requiremin'];
871
		} else if (isset($appInfo['require'])) {
872
			$requireMin = $appInfo['require'];
873
		}
874
875
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
876
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
877 View Code Duplication
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
878
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
879
		} else if (isset($appInfo['requiremax'])) {
880
			$requireMax = $appInfo['requiremax'];
881
		}
882
883
		if (is_array($ocVersion)) {
884
			$ocVersion = implode('.', $ocVersion);
885
		}
886
887 View Code Duplication
		if (!empty($requireMin)
888
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
889
		) {
890
891
			return false;
892
		}
893
894 View Code Duplication
		if (!empty($requireMax)
895
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
896
		) {
897
			return false;
898
		}
899
900
		return true;
901
	}
902
903
	/**
904
	 * get the installed version of all apps
905
	 */
906
	public static function getAppVersions() {
907
		static $versions;
908
909
		if(!$versions) {
910
			$appConfig = \OC::$server->getAppConfig();
911
			$versions = $appConfig->getValues(false, 'installed_version');
912
		}
913
		return $versions;
914
	}
915
916
	/**
917
	 * update the database for the app and call the update script
918
	 *
919
	 * @param string $appId
920
	 * @return bool
921
	 */
922
	public static function updateApp($appId) {
923
		$appPath = self::getAppPath($appId);
924
		if($appPath === false) {
925
			return false;
926
		}
927
		self::registerAutoloading($appId, $appPath);
928
929
		$appData = self::getAppInfo($appId);
930
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
931
932
		if (file_exists($appPath . '/appinfo/database.xml')) {
933
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
934
		} else {
935
			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
936
			$ms->migrate();
937
		}
938
939
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
940
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
941
		// update appversion in app manager
942
		\OC::$server->getAppManager()->getAppVersion($appId, false);
943
944
		// run upgrade code
945
		if (file_exists($appPath . '/appinfo/update.php')) {
946
			self::loadApp($appId);
947
			include $appPath . '/appinfo/update.php';
948
		}
949
		self::setupBackgroundJobs($appData['background-jobs']);
950
951
		//set remote/public handlers
952
		if (array_key_exists('ocsid', $appData)) {
953
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
954 View Code Duplication
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
955
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
956
		}
957
		foreach ($appData['remote'] as $name => $path) {
958
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
959
		}
960 View Code Duplication
		foreach ($appData['public'] as $name => $path) {
961
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
962
		}
963
964
		self::setAppTypes($appId);
965
966
		$version = \OC_App::getAppVersion($appId);
967
		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
968
969
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
970
			ManagerEvent::EVENT_APP_UPDATE, $appId
971
		));
972
973
		return true;
974
	}
975
976
	/**
977
	 * @param string $appId
978
	 * @param string[] $steps
979
	 * @throws \OC\NeedsUpdateException
980
	 */
981
	public static function executeRepairSteps($appId, array $steps) {
982
		if (empty($steps)) {
983
			return;
984
		}
985
		// load the app
986
		self::loadApp($appId);
987
988
		$dispatcher = OC::$server->getEventDispatcher();
989
990
		// load the steps
991
		$r = new Repair([], $dispatcher);
992
		foreach ($steps as $step) {
993
			try {
994
				$r->addStep($step);
995
			} catch (Exception $ex) {
996
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
997
				\OC::$server->getLogger()->logException($ex);
998
			}
999
		}
1000
		// run the steps
1001
		$r->run();
1002
	}
1003
1004
	public static function setupBackgroundJobs(array $jobs) {
1005
		$queue = \OC::$server->getJobList();
1006
		foreach ($jobs as $job) {
1007
			$queue->add($job);
1008
		}
1009
	}
1010
1011
	/**
1012
	 * @param string $appId
1013
	 * @param string[] $steps
1014
	 */
1015
	private static function setupLiveMigrations($appId, array $steps) {
1016
		$queue = \OC::$server->getJobList();
1017
		foreach ($steps as $step) {
1018
			$queue->add('OC\Migration\BackgroundRepair', [
1019
				'app' => $appId,
1020
				'step' => $step]);
1021
		}
1022
	}
1023
1024
	/**
1025
	 * @param string $appId
1026
	 * @return \OC\Files\View|false
1027
	 */
1028
	public static function getStorage($appId) {
1029
		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
1030
			if (\OC::$server->getUserSession()->isLoggedIn()) {
1031
				$view = new \OC\Files\View('/' . OC_User::getUser());
1032
				if (!$view->file_exists($appId)) {
1033
					$view->mkdir($appId);
1034
				}
1035
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1036
			} else {
1037
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', \OCP\Util::ERROR);
1038
				return false;
1039
			}
1040
		} else {
1041
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', \OCP\Util::ERROR);
1042
			return false;
1043
		}
1044
	}
1045
1046
	protected static function findBestL10NOption($options, $lang) {
1047
		$fallback = $similarLangFallback = $englishFallback = false;
1048
1049
		$lang = strtolower($lang);
1050
		$similarLang = $lang;
1051
		if (strpos($similarLang, '_')) {
1052
			// For "de_DE" we want to find "de" and the other way around
1053
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1054
		}
1055
1056
		foreach ($options as $option) {
1057
			if (is_array($option)) {
1058
				if ($fallback === false) {
1059
					$fallback = $option['@value'];
1060
				}
1061
1062
				if (!isset($option['@attributes']['lang'])) {
1063
					continue;
1064
				}
1065
1066
				$attributeLang = strtolower($option['@attributes']['lang']);
1067
				if ($attributeLang === $lang) {
1068
					return $option['@value'];
1069
				}
1070
1071
				if ($attributeLang === $similarLang) {
1072
					$similarLangFallback = $option['@value'];
1073
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1074
					if ($similarLangFallback === false) {
1075
						$similarLangFallback =  $option['@value'];
1076
					}
1077
				}
1078
			} else {
1079
				$englishFallback = $option;
1080
			}
1081
		}
1082
1083
		if ($similarLangFallback !== false) {
1084
			return $similarLangFallback;
1085
		} else if ($englishFallback !== false) {
1086
			return $englishFallback;
1087
		}
1088
		return (string) $fallback;
1089
	}
1090
1091
	/**
1092
	 * parses the app data array and enhanced the 'description' value
1093
	 *
1094
	 * @param array $data the app data
1095
	 * @param string $lang
1096
	 * @return array improved app data
1097
	 */
1098
	public static function parseAppInfo(array $data, $lang = null) {
1099
1100 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...
1101
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1102
		}
1103 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...
1104
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1105
		}
1106
		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...
1107
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1108
		} else if (isset($data['description']) && is_string($data['description'])) {
1109
			$data['description'] = trim($data['description']);
1110
		} else  {
1111
			$data['description'] = '';
1112
		}
1113
1114
		return $data;
1115
	}
1116
1117
	/**
1118
	 * @param \OCP\IConfig $config
1119
	 * @param \OCP\IL10N $l
1120
	 * @param array $info
1121
	 * @throws \Exception
1122
	 */
1123
	public static function checkAppDependencies($config, $l, $info) {
1124
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1125
		$missing = $dependencyAnalyzer->analyze($info);
1126
		if (!empty($missing)) {
1127
			$missingMsg = implode(PHP_EOL, $missing);
1128
			throw new \Exception(
1129
				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1130
					[$info['name'], $missingMsg]
1131
				)
1132
			);
1133
		}
1134
	}
1135
}
1136