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