Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
created

OC_App::findAppInDirectories()   B

Complexity

Conditions 10
Paths 17

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 26
nc 17
nop 1
dl 0
loc 37
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 * @copyright Copyright (c) 2016, Lukas Reschke <[email protected]>
6
 *
7
 * @author Arthur Schiwon <[email protected]>
8
 * @author Bart Visscher <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Björn Schießle <[email protected]>
11
 * @author Borjan Tchakaloff <[email protected]>
12
 * @author Brice Maron <[email protected]>
13
 * @author Christopher Schäpers <[email protected]>
14
 * @author Felix Moeller <[email protected]>
15
 * @author Frank Karlitschek <[email protected]>
16
 * @author Georg Ehrke <[email protected]>
17
 * @author Jakob Sack <[email protected]>
18
 * @author Joas Schilling <[email protected]>
19
 * @author Julius Haertl <[email protected]>
20
 * @author Julius Härtl <[email protected]>
21
 * @author Jörn Friedrich Dreyer <[email protected]>
22
 * @author Kamil Domanski <[email protected]>
23
 * @author Klaas Freitag <[email protected]>
24
 * @author Lukas Reschke <[email protected]>
25
 * @author Markus Goetz <[email protected]>
26
 * @author Morris Jobke <[email protected]>
27
 * @author RealRancor <[email protected]>
28
 * @author Robin Appelman <[email protected]>
29
 * @author Robin McCorkell <[email protected]>
30
 * @author Roeland Jago Douma <[email protected]>
31
 * @author Sam Tuke <[email protected]>
32
 * @author Sebastian Wessalowski <[email protected]>
33
 * @author Thomas Müller <[email protected]>
34
 * @author Thomas Tanghus <[email protected]>
35
 * @author Tom Needham <[email protected]>
36
 * @author Vincent Petry <[email protected]>
37
 *
38
 * @license AGPL-3.0
39
 *
40
 * This code is free software: you can redistribute it and/or modify
41
 * it under the terms of the GNU Affero General Public License, version 3,
42
 * as published by the Free Software Foundation.
43
 *
44
 * This program is distributed in the hope that it will be useful,
45
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
46
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47
 * GNU Affero General Public License for more details.
48
 *
49
 * You should have received a copy of the GNU Affero General Public License, version 3,
50
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
51
 *
52
 */
53
use OC\App\DependencyAnalyzer;
54
use OC\App\Platform;
55
use OC\DB\MigrationService;
56
use OC\Installer;
57
use OC\Repair;
58
use OCP\App\ManagerEvent;
59
use OCP\ILogger;
60
61
/**
62
 * This class manages the apps. It allows them to register and integrate in the
63
 * ownCloud ecosystem. Furthermore, this class is responsible for installing,
64
 * upgrading and removing apps.
65
 */
66
class OC_App {
67
	static private $adminForms = [];
68
	static private $personalForms = [];
69
	static private $appTypes = [];
70
	static private $loadedApps = [];
71
	static private $altLogin = [];
72
	static private $alreadyRegistered = [];
73
	static public $autoDisabledApps = [];
74
	const officialApp = 200;
75
76
	/**
77
	 * clean the appId
78
	 *
79
	 * @param string $app AppId that needs to be cleaned
80
	 * @return string
81
	 */
82
	public static function cleanAppId(string $app): string {
83
		return str_replace(array('\0', '/', '\\', '..'), '', $app);
84
	}
85
86
	/**
87
	 * Check if an app is loaded
88
	 *
89
	 * @param string $app
90
	 * @return bool
91
	 */
92
	public static function isAppLoaded(string $app): bool {
93
		return in_array($app, self::$loadedApps, true);
94
	}
95
96
	/**
97
	 * loads all apps
98
	 *
99
	 * @param string[] $types
100
	 * @return bool
101
	 *
102
	 * This function walks through the ownCloud directory and loads all apps
103
	 * it can find. A directory contains an app if the file /appinfo/info.xml
104
	 * exists.
105
	 *
106
	 * if $types is set to non-empty array, only apps of those types will be loaded
107
	 */
108
	public static function loadApps(array $types = []): bool {
109
		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
110
			return false;
111
		}
112
		// Load the enabled apps here
113
		$apps = self::getEnabledApps();
114
115
		// Add each apps' folder as allowed class path
116
		foreach($apps as $app) {
117
			$path = self::getAppPath($app);
118
			if($path !== false) {
119
				self::registerAutoloading($app, $path);
120
			}
121
		}
122
123
		// prevent app.php from printing output
124
		ob_start();
125
		foreach ($apps as $app) {
126
			if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
127
				self::loadApp($app);
128
			}
129
		}
130
		ob_end_clean();
131
132
		return true;
133
	}
134
135
	/**
136
	 * load a single app
137
	 *
138
	 * @param string $app
139
	 * @throws Exception
140
	 */
141
	public static function loadApp(string $app) {
142
		self::$loadedApps[] = $app;
143
		$appPath = self::getAppPath($app);
144
		if($appPath === false) {
145
			return;
146
		}
147
148
		// in case someone calls loadApp() directly
149
		self::registerAutoloading($app, $appPath);
150
151
		if (is_file($appPath . '/appinfo/app.php')) {
152
			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
153
			try {
154
				self::requireAppFile($app);
155
			} catch (Throwable $ex) {
156
				\OC::$server->getLogger()->logException($ex);
157
				if (!\OC::$server->getAppManager()->isShipped($app)) {
158
					// Only disable apps which are not shipped
159
					\OC::$server->getAppManager()->disableApp($app);
160
					self::$autoDisabledApps[] = $app;
161
				}
162
			}
163
			\OC::$server->getEventLogger()->end('load_app_' . $app);
164
		}
165
166
		$info = self::getAppInfo($app);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppInfo() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppInfo() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

166
		$info = /** @scrutinizer ignore-deprecated */ self::getAppInfo($app);

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

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

Loading history...
167
		if (!empty($info['activity']['filters'])) {
168
			foreach ($info['activity']['filters'] as $filter) {
169
				\OC::$server->getActivityManager()->registerFilter($filter);
170
			}
171
		}
172
		if (!empty($info['activity']['settings'])) {
173
			foreach ($info['activity']['settings'] as $setting) {
174
				\OC::$server->getActivityManager()->registerSetting($setting);
175
			}
176
		}
177
		if (!empty($info['activity']['providers'])) {
178
			foreach ($info['activity']['providers'] as $provider) {
179
				\OC::$server->getActivityManager()->registerProvider($provider);
180
			}
181
		}
182
183
		if (!empty($info['settings']['admin'])) {
184
			foreach ($info['settings']['admin'] as $setting) {
185
				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
186
			}
187
		}
188
		if (!empty($info['settings']['admin-section'])) {
189
			foreach ($info['settings']['admin-section'] as $section) {
190
				\OC::$server->getSettingsManager()->registerSection('admin', $section);
191
			}
192
		}
193
		if (!empty($info['settings']['personal'])) {
194
			foreach ($info['settings']['personal'] as $setting) {
195
				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
196
			}
197
		}
198
		if (!empty($info['settings']['personal-section'])) {
199
			foreach ($info['settings']['personal-section'] as $section) {
200
				\OC::$server->getSettingsManager()->registerSection('personal', $section);
201
			}
202
		}
203
204
		if (!empty($info['collaboration']['plugins'])) {
205
			// deal with one or many plugin entries
206
			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
207
				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
208
			foreach ($plugins as $plugin) {
209
				if($plugin['@attributes']['type'] === 'collaborator-search') {
210
					$pluginInfo = [
211
						'shareType' => $plugin['@attributes']['share-type'],
212
						'class' => $plugin['@value'],
213
					];
214
					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
215
				} else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
216
					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
217
				}
218
			}
219
		}
220
	}
221
222
	/**
223
	 * @internal
224
	 * @param string $app
225
	 * @param string $path
226
	 */
227
	public static function registerAutoloading(string $app, string $path) {
228
		$key = $app . '-' . $path;
229
		if(isset(self::$alreadyRegistered[$key])) {
230
			return;
231
		}
232
233
		self::$alreadyRegistered[$key] = true;
234
235
		// Register on PSR-4 composer autoloader
236
		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
237
		\OC::$server->registerNamespace($app, $appNamespace);
238
239
		if (file_exists($path . '/composer/autoload.php')) {
240
			require_once $path . '/composer/autoload.php';
241
		} else {
242
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
243
			// Register on legacy autoloader
244
			\OC::$loader->addValidRoot($path);
245
		}
246
247
		// Register Test namespace only when testing
248
		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
249
			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
250
		}
251
	}
252
253
	/**
254
	 * Load app.php from the given app
255
	 *
256
	 * @param string $app app name
257
	 * @throws Error
258
	 */
259
	private static function requireAppFile(string $app) {
260
		// encapsulated here to avoid variable scope conflicts
261
		require_once $app . '/appinfo/app.php';
262
	}
263
264
	/**
265
	 * check if an app is of a specific type
266
	 *
267
	 * @param string $app
268
	 * @param array $types
269
	 * @return bool
270
	 */
271
	public static function isType(string $app, array $types): bool {
272
		$appTypes = self::getAppTypes($app);
273
		foreach ($types as $type) {
274
			if (array_search($type, $appTypes) !== false) {
275
				return true;
276
			}
277
		}
278
		return false;
279
	}
280
281
	/**
282
	 * get the types of an app
283
	 *
284
	 * @param string $app
285
	 * @return array
286
	 */
287
	private static function getAppTypes(string $app): array {
288
		//load the cache
289
		if (count(self::$appTypes) == 0) {
290
			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
291
		}
292
293
		if (isset(self::$appTypes[$app])) {
294
			return explode(',', self::$appTypes[$app]);
295
		}
296
297
		return [];
298
	}
299
300
	/**
301
	 * read app types from info.xml and cache them in the database
302
	 */
303
	public static function setAppTypes(string $app) {
304
		$appManager = \OC::$server->getAppManager();
305
		$appData = $appManager->getAppInfo($app);
306
		if(!is_array($appData)) {
307
			return;
308
		}
309
310
		if (isset($appData['types'])) {
311
			$appTypes = implode(',', $appData['types']);
312
		} else {
313
			$appTypes = '';
314
			$appData['types'] = [];
315
		}
316
317
		$config = \OC::$server->getConfig();
318
		$config->setAppValue($app, 'types', $appTypes);
319
320
		if ($appManager->hasProtectedAppType($appData['types'])) {
321
			$enabled = $config->getAppValue($app, 'enabled', 'yes');
322
			if ($enabled !== 'yes' && $enabled !== 'no') {
323
				$config->setAppValue($app, 'enabled', 'yes');
324
			}
325
		}
326
	}
327
328
	/**
329
	 * Returns apps enabled for the current user.
330
	 *
331
	 * @param bool $forceRefresh whether to refresh the cache
332
	 * @param bool $all whether to return apps for all users, not only the
333
	 * currently logged in one
334
	 * @return string[]
335
	 */
336
	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
0 ignored issues
show
Unused Code introduced by
The parameter $forceRefresh is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

336
	public static function getEnabledApps(/** @scrutinizer ignore-unused */ bool $forceRefresh = false, bool $all = false): array {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
337
		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
338
			return [];
339
		}
340
		// in incognito mode or when logged out, $user will be false,
341
		// which is also the case during an upgrade
342
		$appManager = \OC::$server->getAppManager();
343
		if ($all) {
344
			$user = null;
345
		} else {
346
			$user = \OC::$server->getUserSession()->getUser();
347
		}
348
349
		if (is_null($user)) {
350
			$apps = $appManager->getInstalledApps();
351
		} else {
352
			$apps = $appManager->getEnabledAppsForUser($user);
353
		}
354
		$apps = array_filter($apps, function ($app) {
355
			return $app !== 'files';//we add this manually
356
		});
357
		sort($apps);
358
		array_unshift($apps, 'files');
359
		return $apps;
360
	}
361
362
	/**
363
	 * checks whether or not an app is enabled
364
	 *
365
	 * @param string $app app
366
	 * @return bool
367
	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
368
	 *
369
	 * This function checks whether or not an app is enabled.
370
	 */
371
	public static function isEnabled(string $app): bool {
372
		return \OC::$server->getAppManager()->isEnabledForUser($app);
373
	}
374
375
	/**
376
	 * enables an app
377
	 *
378
	 * @param string $appId
379
	 * @param array $groups (optional) when set, only these groups will have access to the app
380
	 * @throws \Exception
381
	 * @return void
382
	 *
383
	 * This function set an app as enabled in appconfig.
384
	 */
385
	public function enable(string $appId,
386
						   array $groups = []) {
387
388
		// Check if app is already downloaded
389
		/** @var Installer $installer */
390
		$installer = \OC::$server->query(Installer::class);
391
		$isDownloaded = $installer->isDownloaded($appId);
392
393
		if(!$isDownloaded) {
394
			$installer->downloadApp($appId);
395
		}
396
397
		$installer->installApp($appId);
398
399
		$appManager = \OC::$server->getAppManager();
400
		if ($groups !== []) {
401
			$groupManager = \OC::$server->getGroupManager();
402
			$groupsList = [];
403
			foreach ($groups as $group) {
404
				$groupItem = $groupManager->get($group);
405
				if ($groupItem instanceof \OCP\IGroup) {
406
					$groupsList[] = $groupManager->get($group);
407
				}
408
			}
409
			$appManager->enableAppForGroups($appId, $groupsList);
410
		} else {
411
			$appManager->enableApp($appId);
412
		}
413
	}
414
415
	/**
416
	 * Get the path where to install apps
417
	 *
418
	 * @return string|false
419
	 */
420
	public static function getInstallPath() {
421
		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing OC::server->getSystemCon...appstoreenabled', true) of type string|true|mixed against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
422
			return false;
423
		}
424
425
		foreach (OC::$APPSROOTS as $dir) {
426
			if (isset($dir['writable']) && $dir['writable'] === true) {
427
				return $dir['path'];
428
			}
429
		}
430
431
		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

431
		/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);

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

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

Loading history...
432
		return null;
433
	}
434
435
436
	/**
437
	 * search for an app in all app-directories
438
	 *
439
	 * @param string $appId
440
	 * @return false|string
441
	 */
442
	public static function findAppInDirectories(string $appId) {
443
		$sanitizedAppId = self::cleanAppId($appId);
444
		if($sanitizedAppId !== $appId) {
445
			return false;
446
		}
447
		static $app_dir = [];
448
449
		if (isset($app_dir[$appId])) {
450
			return $app_dir[$appId];
451
		}
452
453
		$possibleApps = [];
454
		foreach (OC::$APPSROOTS as $dir) {
455
			if (file_exists($dir['path'] . '/' . $appId)) {
456
				$possibleApps[] = $dir;
457
			}
458
		}
459
460
		if (empty($possibleApps)) {
461
			return false;
462
		} elseif (count($possibleApps) === 1) {
463
			$dir = array_shift($possibleApps);
464
			$app_dir[$appId] = $dir;
465
			return $dir;
466
		} else {
467
			$versionToLoad = [];
468
			foreach ($possibleApps as $possibleApp) {
469
				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
470
				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
471
					$versionToLoad = array(
472
						'dir' => $possibleApp,
473
						'version' => $version,
474
					);
475
				}
476
			}
477
			$app_dir[$appId] = $versionToLoad['dir'];
478
			return $versionToLoad['dir'];
479
			//TODO - write test
480
		}
481
	}
482
483
	/**
484
	 * Get the directory for the given app.
485
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
486
	 *
487
	 * @param string $appId
488
	 * @return string|false
489
	 */
490
	public static function getAppPath(string $appId) {
491
		if ($appId === null || trim($appId) === '') {
492
			return false;
493
		}
494
495
		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...
496
			return $dir['path'] . '/' . $appId;
497
		}
498
		return false;
499
	}
500
501
	/**
502
	 * Get the path for the given app on the access
503
	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
504
	 *
505
	 * @param string $appId
506
	 * @return string|false
507
	 */
508
	public static function getAppWebPath(string $appId) {
509
		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...
510
			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
511
		}
512
		return false;
513
	}
514
515
	/**
516
	 * get the last version of the app from appinfo/info.xml
517
	 *
518
	 * @param string $appId
519
	 * @param bool $useCache
520
	 * @return string
521
	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
522
	 */
523
	public static function getAppVersion(string $appId, bool $useCache = true): string {
524
		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
525
	}
526
527
	/**
528
	 * get app's version based on it's path
529
	 *
530
	 * @param string $path
531
	 * @return string
532
	 */
533
	public static function getAppVersionByPath(string $path): string {
534
		$infoFile = $path . '/appinfo/info.xml';
535
		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
536
		return isset($appData['version']) ? $appData['version'] : '';
537
	}
538
539
540
	/**
541
	 * Read all app metadata from the info.xml file
542
	 *
543
	 * @param string $appId id of the app or the path of the info.xml file
544
	 * @param bool $path
545
	 * @param string $lang
546
	 * @return array|null
547
	 * @note all data is read from info.xml, not just pre-defined fields
548
	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
549
	 */
550
	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
551
		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
552
	}
553
554
	/**
555
	 * Returns the navigation
556
	 *
557
	 * @return array
558
	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
559
	 *
560
	 * This function returns an array containing all entries added. The
561
	 * entries are sorted by the key 'order' ascending. Additional to the keys
562
	 * given for each app the following keys exist:
563
	 *   - active: boolean, signals if the user is on this navigation entry
564
	 */
565
	public static function getNavigation(): array {
566
		return OC::$server->getNavigationManager()->getAll();
567
	}
568
569
	/**
570
	 * Returns the Settings Navigation
571
	 *
572
	 * @return string[]
573
	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
574
	 *
575
	 * This function returns an array containing all settings pages added. The
576
	 * entries are sorted by the key 'order' ascending.
577
	 */
578
	public static function getSettingsNavigation(): array {
579
		return OC::$server->getNavigationManager()->getAll('settings');
580
	}
581
582
	/**
583
	 * get the id of loaded app
584
	 *
585
	 * @return string
586
	 */
587
	public static function getCurrentApp(): string {
588
		$request = \OC::$server->getRequest();
589
		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
590
		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
591
		if (empty($topFolder)) {
592
			$path_info = $request->getPathInfo();
593
			if ($path_info) {
594
				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
595
			}
596
		}
597
		if ($topFolder == 'apps') {
598
			$length = strlen($topFolder);
599
			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
600
		} else {
601
			return $topFolder;
602
		}
603
	}
604
605
	/**
606
	 * @param string $type
607
	 * @return array
608
	 */
609
	public static function getForms(string $type): array {
610
		$forms = [];
611
		switch ($type) {
612
			case 'admin':
613
				$source = self::$adminForms;
614
				break;
615
			case 'personal':
616
				$source = self::$personalForms;
617
				break;
618
			default:
619
				return [];
620
		}
621
		foreach ($source as $form) {
622
			$forms[] = include $form;
623
		}
624
		return $forms;
625
	}
626
627
	/**
628
	 * register an admin form to be shown
629
	 *
630
	 * @param string $app
631
	 * @param string $page
632
	 */
633
	public static function registerAdmin(string $app, string $page) {
634
		self::$adminForms[] = $app . '/' . $page . '.php';
635
	}
636
637
	/**
638
	 * register a personal form to be shown
639
	 * @param string $app
640
	 * @param string $page
641
	 */
642
	public static function registerPersonal(string $app, string $page) {
643
		self::$personalForms[] = $app . '/' . $page . '.php';
644
	}
645
646
	/**
647
	 * @param array $entry
648
	 */
649
	public static function registerLogIn(array $entry) {
650
		self::$altLogin[] = $entry;
651
	}
652
653
	/**
654
	 * @return array
655
	 */
656
	public static function getAlternativeLogIns(): array {
657
		return self::$altLogin;
658
	}
659
660
	/**
661
	 * get a list of all apps in the apps folder
662
	 *
663
	 * @return array an array of app names (string IDs)
664
	 * @todo: change the name of this method to getInstalledApps, which is more accurate
665
	 */
666
	public static function getAllApps(): array {
667
668
		$apps = [];
669
670
		foreach (OC::$APPSROOTS as $apps_dir) {
671
			if (!is_readable($apps_dir['path'])) {
672
				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

672
				/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);

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

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

Loading history...
673
				continue;
674
			}
675
			$dh = opendir($apps_dir['path']);
676
677
			if (is_resource($dh)) {
678
				while (($file = readdir($dh)) !== false) {
679
680
					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
681
682
						$apps[] = $file;
683
					}
684
				}
685
			}
686
		}
687
688
		$apps = array_unique($apps);
689
690
		return $apps;
691
	}
692
693
	/**
694
	 * List all apps, this is used in apps.php
695
	 *
696
	 * @return array
697
	 */
698
	public function listAllApps(): array {
699
		$installedApps = OC_App::getAllApps();
700
701
		$appManager = \OC::$server->getAppManager();
702
		//we don't want to show configuration for these
703
		$blacklist = $appManager->getAlwaysEnabledApps();
704
		$appList = [];
705
		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
706
		$urlGenerator = \OC::$server->getURLGenerator();
707
708
		foreach ($installedApps as $app) {
709
			if (array_search($app, $blacklist) === false) {
710
711
				$info = OC_App::getAppInfo($app, false, $langCode);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppInfo() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppInfo() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

711
				$info = /** @scrutinizer ignore-deprecated */ OC_App::getAppInfo($app, false, $langCode);

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

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

Loading history...
712
				if (!is_array($info)) {
713
					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

713
					/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);

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

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

Loading history...
714
					continue;
715
				}
716
717
				if (!isset($info['name'])) {
718
					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

718
					/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);

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

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

Loading history...
719
					continue;
720
				}
721
722
				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
723
				$info['groups'] = null;
724
				if ($enabled === 'yes') {
725
					$active = true;
726
				} else if ($enabled === 'no') {
727
					$active = false;
728
				} else {
729
					$active = true;
730
					$info['groups'] = $enabled;
731
				}
732
733
				$info['active'] = $active;
734
735
				if ($appManager->isShipped($app)) {
736
					$info['internal'] = true;
737
					$info['level'] = self::officialApp;
738
					$info['removable'] = false;
739
				} else {
740
					$info['internal'] = false;
741
					$info['removable'] = true;
742
				}
743
744
				$appPath = self::getAppPath($app);
745
				if($appPath !== false) {
746
					$appIcon = $appPath . '/img/' . $app . '.svg';
747
					if (file_exists($appIcon)) {
748
						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
749
						$info['previewAsIcon'] = true;
750
					} else {
751
						$appIcon = $appPath . '/img/app.svg';
752
						if (file_exists($appIcon)) {
753
							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
754
							$info['previewAsIcon'] = true;
755
						}
756
					}
757
				}
758
				// fix documentation
759
				if (isset($info['documentation']) && is_array($info['documentation'])) {
760
					foreach ($info['documentation'] as $key => $url) {
761
						// If it is not an absolute URL we assume it is a key
762
						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
763
						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
764
							$url = $urlGenerator->linkToDocs($url);
765
						}
766
767
						$info['documentation'][$key] = $url;
768
					}
769
				}
770
771
				$info['version'] = OC_App::getAppVersion($app);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppVersion() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppVersion() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

771
				$info['version'] = /** @scrutinizer ignore-deprecated */ OC_App::getAppVersion($app);

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

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

Loading history...
772
				$appList[] = $info;
773
			}
774
		}
775
776
		return $appList;
777
	}
778
779
	public static function shouldUpgrade(string $app): bool {
780
		$versions = self::getAppVersions();
781
		$currentVersion = OC_App::getAppVersion($app);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppVersion() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppVersion() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

781
		$currentVersion = /** @scrutinizer ignore-deprecated */ OC_App::getAppVersion($app);

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

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

Loading history...
782
		if ($currentVersion && isset($versions[$app])) {
783
			$installedVersion = $versions[$app];
784
			if (!version_compare($currentVersion, $installedVersion, '=')) {
785
				return true;
786
			}
787
		}
788
		return false;
789
	}
790
791
	/**
792
	 * Adjust the number of version parts of $version1 to match
793
	 * the number of version parts of $version2.
794
	 *
795
	 * @param string $version1 version to adjust
796
	 * @param string $version2 version to take the number of parts from
797
	 * @return string shortened $version1
798
	 */
799
	private static function adjustVersionParts(string $version1, string $version2): string {
800
		$version1 = explode('.', $version1);
801
		$version2 = explode('.', $version2);
802
		// reduce $version1 to match the number of parts in $version2
803
		while (count($version1) > count($version2)) {
804
			array_pop($version1);
805
		}
806
		// if $version1 does not have enough parts, add some
807
		while (count($version1) < count($version2)) {
808
			$version1[] = '0';
809
		}
810
		return implode('.', $version1);
811
	}
812
813
	/**
814
	 * Check whether the current ownCloud version matches the given
815
	 * application's version requirements.
816
	 *
817
	 * The comparison is made based on the number of parts that the
818
	 * app info version has. For example for ownCloud 6.0.3 if the
819
	 * app info version is expecting version 6.0, the comparison is
820
	 * made on the first two parts of the ownCloud version.
821
	 * This means that it's possible to specify "requiremin" => 6
822
	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
823
	 *
824
	 * @param string $ocVersion ownCloud version to check against
825
	 * @param array $appInfo app info (from xml)
826
	 *
827
	 * @return boolean true if compatible, otherwise false
828
	 */
829
	public static function isAppCompatible(string $ocVersion, array $appInfo): bool {
830
		$requireMin = '';
831
		$requireMax = '';
832
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
833
			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
834
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
835
			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
836
		} else if (isset($appInfo['requiremin'])) {
837
			$requireMin = $appInfo['requiremin'];
838
		} else if (isset($appInfo['require'])) {
839
			$requireMin = $appInfo['require'];
840
		}
841
842
		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
843
			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
844
		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
845
			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
846
		} else if (isset($appInfo['requiremax'])) {
847
			$requireMax = $appInfo['requiremax'];
848
		}
849
850
		if (!empty($requireMin)
851
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
852
		) {
853
854
			return false;
855
		}
856
857
		if (!empty($requireMax)
858
			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
859
		) {
860
			return false;
861
		}
862
863
		return true;
864
	}
865
866
	/**
867
	 * get the installed version of all apps
868
	 */
869
	public static function getAppVersions() {
870
		static $versions;
871
872
		if(!$versions) {
873
			$appConfig = \OC::$server->getAppConfig();
874
			$versions = $appConfig->getValues(false, 'installed_version');
875
		}
876
		return $versions;
877
	}
878
879
	/**
880
	 * update the database for the app and call the update script
881
	 *
882
	 * @param string $appId
883
	 * @return bool
884
	 */
885
	public static function updateApp(string $appId): bool {
886
		$appPath = self::getAppPath($appId);
887
		if($appPath === false) {
888
			return false;
889
		}
890
		self::registerAutoloading($appId, $appPath);
891
892
		\OC::$server->getAppManager()->clearAppsCache();
893
		$appData = self::getAppInfo($appId);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppInfo() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppInfo() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

893
		$appData = /** @scrutinizer ignore-deprecated */ self::getAppInfo($appId);

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

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

Loading history...
894
		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
895
896
		if (file_exists($appPath . '/appinfo/database.xml')) {
897
			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
898
		} else {
899
			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
900
			$ms->migrate();
901
		}
902
903
		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
904
		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
905
		// update appversion in app manager
906
		\OC::$server->getAppManager()->clearAppsCache();
907
		\OC::$server->getAppManager()->getAppVersion($appId, false);
908
909
		// run upgrade code
910
		if (file_exists($appPath . '/appinfo/update.php')) {
911
			self::loadApp($appId);
912
			include $appPath . '/appinfo/update.php';
913
		}
914
		self::setupBackgroundJobs($appData['background-jobs']);
915
916
		//set remote/public handlers
917
		if (array_key_exists('ocsid', $appData)) {
0 ignored issues
show
Bug introduced by
It seems like $appData can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

917
		if (array_key_exists('ocsid', /** @scrutinizer ignore-type */ $appData)) {
Loading history...
918
			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
919
		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
0 ignored issues
show
introduced by
The condition OC::server->getConfig()-...'ocsid', null) !== null is always true.
Loading history...
920
			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
921
		}
922
		foreach ($appData['remote'] as $name => $path) {
923
			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
924
		}
925
		foreach ($appData['public'] as $name => $path) {
926
			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
927
		}
928
929
		self::setAppTypes($appId);
930
931
		$version = \OC_App::getAppVersion($appId);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppVersion() has been deprecated: 14.0.0 use \OC::$server->getAppManager()->getAppVersion() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

931
		$version = /** @scrutinizer ignore-deprecated */ \OC_App::getAppVersion($appId);

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

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

Loading history...
932
		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
933
934
		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
935
			ManagerEvent::EVENT_APP_UPDATE, $appId
936
		));
937
938
		return true;
939
	}
940
941
	/**
942
	 * @param string $appId
943
	 * @param string[] $steps
944
	 * @throws \OC\NeedsUpdateException
945
	 */
946
	public static function executeRepairSteps(string $appId, array $steps) {
947
		if (empty($steps)) {
948
			return;
949
		}
950
		// load the app
951
		self::loadApp($appId);
952
953
		$dispatcher = OC::$server->getEventDispatcher();
954
955
		// load the steps
956
		$r = new Repair([], $dispatcher);
957
		foreach ($steps as $step) {
958
			try {
959
				$r->addStep($step);
960
			} catch (Exception $ex) {
961
				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
962
				\OC::$server->getLogger()->logException($ex);
963
			}
964
		}
965
		// run the steps
966
		$r->run();
967
	}
968
969
	public static function setupBackgroundJobs(array $jobs) {
970
		$queue = \OC::$server->getJobList();
971
		foreach ($jobs as $job) {
972
			$queue->add($job);
973
		}
974
	}
975
976
	/**
977
	 * @param string $appId
978
	 * @param string[] $steps
979
	 */
980
	private static function setupLiveMigrations(string $appId, array $steps) {
981
		$queue = \OC::$server->getJobList();
982
		foreach ($steps as $step) {
983
			$queue->add('OC\Migration\BackgroundRepair', [
984
				'app' => $appId,
985
				'step' => $step]);
986
		}
987
	}
988
989
	/**
990
	 * @param string $appId
991
	 * @return \OC\Files\View|false
992
	 */
993
	public static function getStorage(string $appId) {
994
		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
995
			if (\OC::$server->getUserSession()->isLoggedIn()) {
996
				$view = new \OC\Files\View('/' . OC_User::getUser());
997
				if (!$view->file_exists($appId)) {
998
					$view->mkdir($appId);
999
				}
1000
				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
1001
			} else {
1002
				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1002
				/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);

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

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

Loading history...
1003
				return false;
1004
			}
1005
		} else {
1006
			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1006
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);

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

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

Loading history...
1007
			return false;
1008
		}
1009
	}
1010
1011
	protected static function findBestL10NOption(array $options, string $lang): string {
1012
		// only a single option
1013
		if (isset($options['@value'])) {
1014
			return $options['@value'];
1015
		}
1016
1017
		$fallback = $similarLangFallback = $englishFallback = false;
1018
1019
		$lang = strtolower($lang);
1020
		$similarLang = $lang;
1021
		if (strpos($similarLang, '_')) {
1022
			// For "de_DE" we want to find "de" and the other way around
1023
			$similarLang = substr($lang, 0, strpos($lang, '_'));
1024
		}
1025
1026
		foreach ($options as $option) {
1027
			if (is_array($option)) {
1028
				if ($fallback === false) {
1029
					$fallback = $option['@value'];
1030
				}
1031
1032
				if (!isset($option['@attributes']['lang'])) {
1033
					continue;
1034
				}
1035
1036
				$attributeLang = strtolower($option['@attributes']['lang']);
1037
				if ($attributeLang === $lang) {
1038
					return $option['@value'];
1039
				}
1040
1041
				if ($attributeLang === $similarLang) {
1042
					$similarLangFallback = $option['@value'];
1043
				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1044
					if ($similarLangFallback === false) {
1045
						$similarLangFallback =  $option['@value'];
1046
					}
1047
				}
1048
			} else {
1049
				$englishFallback = $option;
1050
			}
1051
		}
1052
1053
		if ($similarLangFallback !== false) {
1054
			return $similarLangFallback;
1055
		} else if ($englishFallback !== false) {
1056
			return $englishFallback;
1057
		}
1058
		return (string) $fallback;
1059
	}
1060
1061
	/**
1062
	 * parses the app data array and enhanced the 'description' value
1063
	 *
1064
	 * @param array $data the app data
1065
	 * @param string $lang
1066
	 * @return array improved app data
1067
	 */
1068
	public static function parseAppInfo(array $data, $lang = null): array {
1069
1070
		if ($lang && isset($data['name']) && is_array($data['name'])) {
1071
			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1072
		}
1073
		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1074
			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1075
		}
1076
		if ($lang && isset($data['description']) && is_array($data['description'])) {
1077
			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1078
		} else if (isset($data['description']) && is_string($data['description'])) {
1079
			$data['description'] = trim($data['description']);
1080
		} else  {
1081
			$data['description'] = '';
1082
		}
1083
1084
		return $data;
1085
	}
1086
1087
	/**
1088
	 * @param \OCP\IConfig $config
1089
	 * @param \OCP\IL10N $l
1090
	 * @param array $info
1091
	 * @throws \Exception
1092
	 */
1093
	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info) {
1094
		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1095
		$missing = $dependencyAnalyzer->analyze($info);
1096
		if (!empty($missing)) {
1097
			$missingMsg = implode(PHP_EOL, $missing);
1098
			throw new \Exception(
1099
				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1100
					[$info['name'], $missingMsg]
1101
				)
1102
			);
1103
		}
1104
	}
1105
}
1106