Passed
Push — master ( 496a8d...f0238f )
by Joas
17:19 queued 13s
created
lib/private/legacy/OC_App.php 1 patch
Indentation   +954 added lines, -954 removed lines patch added patch discarded remove patch
@@ -73,958 +73,958 @@
 block discarded – undo
73 73
  * upgrading and removing apps.
74 74
  */
75 75
 class OC_App {
76
-	private static $adminForms = [];
77
-	private static $personalForms = [];
78
-	private static $altLogin = [];
79
-	private static $alreadyRegistered = [];
80
-	public const supportedApp = 300;
81
-	public const officialApp = 200;
82
-
83
-	/**
84
-	 * clean the appId
85
-	 *
86
-	 * @psalm-taint-escape file
87
-	 * @psalm-taint-escape include
88
-	 * @psalm-taint-escape html
89
-	 * @psalm-taint-escape has_quotes
90
-	 *
91
-	 * @param string $app AppId that needs to be cleaned
92
-	 * @return string
93
-	 */
94
-	public static function cleanAppId(string $app): string {
95
-		return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
96
-	}
97
-
98
-	/**
99
-	 * Check if an app is loaded
100
-	 *
101
-	 * @param string $app
102
-	 * @return bool
103
-	 * @deprecated 27.0.0 use IAppManager::isAppLoaded
104
-	 */
105
-	public static function isAppLoaded(string $app): bool {
106
-		return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
107
-	}
108
-
109
-	/**
110
-	 * loads all apps
111
-	 *
112
-	 * @param string[] $types
113
-	 * @return bool
114
-	 *
115
-	 * This function walks through the ownCloud directory and loads all apps
116
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
117
-	 * exists.
118
-	 *
119
-	 * if $types is set to non-empty array, only apps of those types will be loaded
120
-	 */
121
-	public static function loadApps(array $types = []): bool {
122
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
123
-			// This should be done before calling this method so that appmanager can be used
124
-			return false;
125
-		}
126
-		return \OC::$server->get(IAppManager::class)->loadApps($types);
127
-	}
128
-
129
-	/**
130
-	 * load a single app
131
-	 *
132
-	 * @param string $app
133
-	 * @throws Exception
134
-	 * @deprecated 27.0.0 use IAppManager::loadApp
135
-	 */
136
-	public static function loadApp(string $app): void {
137
-		\OC::$server->get(IAppManager::class)->loadApp($app);
138
-	}
139
-
140
-	/**
141
-	 * @internal
142
-	 * @param string $app
143
-	 * @param string $path
144
-	 * @param bool $force
145
-	 */
146
-	public static function registerAutoloading(string $app, string $path, bool $force = false) {
147
-		$key = $app . '-' . $path;
148
-		if (!$force && isset(self::$alreadyRegistered[$key])) {
149
-			return;
150
-		}
151
-
152
-		self::$alreadyRegistered[$key] = true;
153
-
154
-		// Register on PSR-4 composer autoloader
155
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
156
-		\OC::$server->registerNamespace($app, $appNamespace);
157
-
158
-		if (file_exists($path . '/composer/autoload.php')) {
159
-			require_once $path . '/composer/autoload.php';
160
-		} else {
161
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
162
-		}
163
-
164
-		// Register Test namespace only when testing
165
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
166
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
167
-		}
168
-	}
169
-
170
-	/**
171
-	 * check if an app is of a specific type
172
-	 *
173
-	 * @param string $app
174
-	 * @param array $types
175
-	 * @return bool
176
-	 * @deprecated 27.0.0 use IAppManager::isType
177
-	 */
178
-	public static function isType(string $app, array $types): bool {
179
-		return \OC::$server->get(IAppManager::class)->isType($app, $types);
180
-	}
181
-
182
-	/**
183
-	 * read app types from info.xml and cache them in the database
184
-	 */
185
-	public static function setAppTypes(string $app) {
186
-		$appManager = \OC::$server->getAppManager();
187
-		$appData = $appManager->getAppInfo($app);
188
-		if (!is_array($appData)) {
189
-			return;
190
-		}
191
-
192
-		if (isset($appData['types'])) {
193
-			$appTypes = implode(',', $appData['types']);
194
-		} else {
195
-			$appTypes = '';
196
-			$appData['types'] = [];
197
-		}
198
-
199
-		$config = \OC::$server->getConfig();
200
-		$config->setAppValue($app, 'types', $appTypes);
201
-
202
-		if ($appManager->hasProtectedAppType($appData['types'])) {
203
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
204
-			if ($enabled !== 'yes' && $enabled !== 'no') {
205
-				$config->setAppValue($app, 'enabled', 'yes');
206
-			}
207
-		}
208
-	}
209
-
210
-	/**
211
-	 * Returns apps enabled for the current user.
212
-	 *
213
-	 * @param bool $forceRefresh whether to refresh the cache
214
-	 * @param bool $all whether to return apps for all users, not only the
215
-	 * currently logged in one
216
-	 * @return string[]
217
-	 */
218
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
219
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
220
-			return [];
221
-		}
222
-		// in incognito mode or when logged out, $user will be false,
223
-		// which is also the case during an upgrade
224
-		$appManager = \OC::$server->getAppManager();
225
-		if ($all) {
226
-			$user = null;
227
-		} else {
228
-			$user = \OC::$server->getUserSession()->getUser();
229
-		}
230
-
231
-		if (is_null($user)) {
232
-			$apps = $appManager->getInstalledApps();
233
-		} else {
234
-			$apps = $appManager->getEnabledAppsForUser($user);
235
-		}
236
-		$apps = array_filter($apps, function ($app) {
237
-			return $app !== 'files';//we add this manually
238
-		});
239
-		sort($apps);
240
-		array_unshift($apps, 'files');
241
-		return $apps;
242
-	}
243
-
244
-	/**
245
-	 * enables an app
246
-	 *
247
-	 * @param string $appId
248
-	 * @param array $groups (optional) when set, only these groups will have access to the app
249
-	 * @throws \Exception
250
-	 * @return void
251
-	 *
252
-	 * This function set an app as enabled in appconfig.
253
-	 */
254
-	public function enable(string $appId,
255
-						   array $groups = []) {
256
-		// Check if app is already downloaded
257
-		/** @var Installer $installer */
258
-		$installer = \OC::$server->query(Installer::class);
259
-		$isDownloaded = $installer->isDownloaded($appId);
260
-
261
-		if (!$isDownloaded) {
262
-			$installer->downloadApp($appId);
263
-		}
264
-
265
-		$installer->installApp($appId);
266
-
267
-		$appManager = \OC::$server->getAppManager();
268
-		if ($groups !== []) {
269
-			$groupManager = \OC::$server->getGroupManager();
270
-			$groupsList = [];
271
-			foreach ($groups as $group) {
272
-				$groupItem = $groupManager->get($group);
273
-				if ($groupItem instanceof \OCP\IGroup) {
274
-					$groupsList[] = $groupManager->get($group);
275
-				}
276
-			}
277
-			$appManager->enableAppForGroups($appId, $groupsList);
278
-		} else {
279
-			$appManager->enableApp($appId);
280
-		}
281
-	}
282
-
283
-	/**
284
-	 * Get the path where to install apps
285
-	 *
286
-	 * @return string|false
287
-	 */
288
-	public static function getInstallPath() {
289
-		foreach (OC::$APPSROOTS as $dir) {
290
-			if (isset($dir['writable']) && $dir['writable'] === true) {
291
-				return $dir['path'];
292
-			}
293
-		}
294
-
295
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
296
-		return null;
297
-	}
298
-
299
-
300
-	/**
301
-	 * search for an app in all app-directories
302
-	 *
303
-	 * @param string $appId
304
-	 * @param bool $ignoreCache ignore cache and rebuild it
305
-	 * @return false|string
306
-	 */
307
-	public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
308
-		$sanitizedAppId = self::cleanAppId($appId);
309
-		if ($sanitizedAppId !== $appId) {
310
-			return false;
311
-		}
312
-		static $app_dir = [];
313
-
314
-		if (isset($app_dir[$appId]) && !$ignoreCache) {
315
-			return $app_dir[$appId];
316
-		}
317
-
318
-		$possibleApps = [];
319
-		foreach (OC::$APPSROOTS as $dir) {
320
-			if (file_exists($dir['path'] . '/' . $appId)) {
321
-				$possibleApps[] = $dir;
322
-			}
323
-		}
324
-
325
-		if (empty($possibleApps)) {
326
-			return false;
327
-		} elseif (count($possibleApps) === 1) {
328
-			$dir = array_shift($possibleApps);
329
-			$app_dir[$appId] = $dir;
330
-			return $dir;
331
-		} else {
332
-			$versionToLoad = [];
333
-			foreach ($possibleApps as $possibleApp) {
334
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
335
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
336
-					$versionToLoad = [
337
-						'dir' => $possibleApp,
338
-						'version' => $version,
339
-					];
340
-				}
341
-			}
342
-			$app_dir[$appId] = $versionToLoad['dir'];
343
-			return $versionToLoad['dir'];
344
-			//TODO - write test
345
-		}
346
-	}
347
-
348
-	/**
349
-	 * Get the directory for the given app.
350
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
351
-	 *
352
-	 * @psalm-taint-specialize
353
-	 *
354
-	 * @param string $appId
355
-	 * @param bool $refreshAppPath should be set to true only during install/upgrade
356
-	 * @return string|false
357
-	 * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
358
-	 */
359
-	public static function getAppPath(string $appId, bool $refreshAppPath = false) {
360
-		if ($appId === null || trim($appId) === '') {
361
-			return false;
362
-		}
363
-
364
-		if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
365
-			return $dir['path'] . '/' . $appId;
366
-		}
367
-		return false;
368
-	}
369
-
370
-	/**
371
-	 * Get the path for the given app on the access
372
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
373
-	 *
374
-	 * @param string $appId
375
-	 * @return string|false
376
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
377
-	 */
378
-	public static function getAppWebPath(string $appId) {
379
-		if (($dir = self::findAppInDirectories($appId)) != false) {
380
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
381
-		}
382
-		return false;
383
-	}
384
-
385
-	/**
386
-	 * get the last version of the app from appinfo/info.xml
387
-	 *
388
-	 * @param string $appId
389
-	 * @param bool $useCache
390
-	 * @return string
391
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
392
-	 */
393
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
394
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
395
-	}
396
-
397
-	/**
398
-	 * get app's version based on it's path
399
-	 *
400
-	 * @param string $path
401
-	 * @return string
402
-	 */
403
-	public static function getAppVersionByPath(string $path): string {
404
-		$infoFile = $path . '/appinfo/info.xml';
405
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
406
-		return isset($appData['version']) ? $appData['version'] : '';
407
-	}
408
-
409
-
410
-	/**
411
-	 * Read all app metadata from the info.xml file
412
-	 *
413
-	 * @param string $appId id of the app or the path of the info.xml file
414
-	 * @param bool $path
415
-	 * @param string $lang
416
-	 * @return array|null
417
-	 * @note all data is read from info.xml, not just pre-defined fields
418
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
419
-	 */
420
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
421
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
422
-	}
423
-
424
-	/**
425
-	 * Returns the navigation
426
-	 *
427
-	 * @return array
428
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
429
-	 *
430
-	 * This function returns an array containing all entries added. The
431
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
432
-	 * given for each app the following keys exist:
433
-	 *   - active: boolean, signals if the user is on this navigation entry
434
-	 */
435
-	public static function getNavigation(): array {
436
-		return OC::$server->getNavigationManager()->getAll();
437
-	}
438
-
439
-	/**
440
-	 * Returns the Settings Navigation
441
-	 *
442
-	 * @return string[]
443
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
444
-	 *
445
-	 * This function returns an array containing all settings pages added. The
446
-	 * entries are sorted by the key 'order' ascending.
447
-	 */
448
-	public static function getSettingsNavigation(): array {
449
-		return OC::$server->getNavigationManager()->getAll('settings');
450
-	}
451
-
452
-	/**
453
-	 * get the id of loaded app
454
-	 *
455
-	 * @return string
456
-	 */
457
-	public static function getCurrentApp(): string {
458
-		if (\OC::$CLI) {
459
-			return '';
460
-		}
461
-
462
-		$request = \OC::$server->getRequest();
463
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
464
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
465
-		if (empty($topFolder)) {
466
-			try {
467
-				$path_info = $request->getPathInfo();
468
-			} catch (Exception $e) {
469
-				// Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
470
-				\OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
471
-				return '';
472
-			}
473
-			if ($path_info) {
474
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
475
-			}
476
-		}
477
-		if ($topFolder == 'apps') {
478
-			$length = strlen($topFolder);
479
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
480
-		} else {
481
-			return $topFolder;
482
-		}
483
-	}
484
-
485
-	/**
486
-	 * @param string $type
487
-	 * @return array
488
-	 */
489
-	public static function getForms(string $type): array {
490
-		$forms = [];
491
-		switch ($type) {
492
-			case 'admin':
493
-				$source = self::$adminForms;
494
-				break;
495
-			case 'personal':
496
-				$source = self::$personalForms;
497
-				break;
498
-			default:
499
-				return [];
500
-		}
501
-		foreach ($source as $form) {
502
-			$forms[] = include $form;
503
-		}
504
-		return $forms;
505
-	}
506
-
507
-	/**
508
-	 * @param array $entry
509
-	 * @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
510
-	 */
511
-	public static function registerLogIn(array $entry) {
512
-		\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');
513
-		self::$altLogin[] = $entry;
514
-	}
515
-
516
-	/**
517
-	 * @return array
518
-	 */
519
-	public static function getAlternativeLogIns(): array {
520
-		/** @var Coordinator $bootstrapCoordinator */
521
-		$bootstrapCoordinator = \OC::$server->query(Coordinator::class);
522
-
523
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
524
-			if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
525
-				\OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
526
-					'option' => $registration->getService(),
527
-					'interface' => IAlternativeLogin::class,
528
-					'app' => $registration->getAppId(),
529
-				]);
530
-				continue;
531
-			}
532
-
533
-			try {
534
-				/** @var IAlternativeLogin $provider */
535
-				$provider = \OC::$server->query($registration->getService());
536
-			} catch (QueryException $e) {
537
-				\OC::$server->getLogger()->logException($e, [
538
-					'message' => 'Alternative login option {option} can not be initialised.',
539
-					'option' => $registration->getService(),
540
-					'app' => $registration->getAppId(),
541
-				]);
542
-			}
543
-
544
-			try {
545
-				$provider->load();
546
-
547
-				self::$altLogin[] = [
548
-					'name' => $provider->getLabel(),
549
-					'href' => $provider->getLink(),
550
-					'class' => $provider->getClass(),
551
-				];
552
-			} catch (Throwable $e) {
553
-				\OC::$server->getLogger()->logException($e, [
554
-					'message' => 'Alternative login option {option} had an error while loading.',
555
-					'option' => $registration->getService(),
556
-					'app' => $registration->getAppId(),
557
-				]);
558
-			}
559
-		}
560
-
561
-		return self::$altLogin;
562
-	}
563
-
564
-	/**
565
-	 * get a list of all apps in the apps folder
566
-	 *
567
-	 * @return string[] an array of app names (string IDs)
568
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
569
-	 */
570
-	public static function getAllApps(): array {
571
-		$apps = [];
572
-
573
-		foreach (OC::$APPSROOTS as $apps_dir) {
574
-			if (!is_readable($apps_dir['path'])) {
575
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
576
-				continue;
577
-			}
578
-			$dh = opendir($apps_dir['path']);
579
-
580
-			if (is_resource($dh)) {
581
-				while (($file = readdir($dh)) !== false) {
582
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
583
-						$apps[] = $file;
584
-					}
585
-				}
586
-			}
587
-		}
588
-
589
-		$apps = array_unique($apps);
590
-
591
-		return $apps;
592
-	}
593
-
594
-	/**
595
-	 * List all supported apps
596
-	 *
597
-	 * @return array
598
-	 */
599
-	public function getSupportedApps(): array {
600
-		/** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
601
-		$subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
602
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
603
-		return $supportedApps;
604
-	}
605
-
606
-	/**
607
-	 * List all apps, this is used in apps.php
608
-	 *
609
-	 * @return array
610
-	 */
611
-	public function listAllApps(): array {
612
-		$installedApps = OC_App::getAllApps();
613
-
614
-		$appManager = \OC::$server->getAppManager();
615
-		//we don't want to show configuration for these
616
-		$blacklist = $appManager->getAlwaysEnabledApps();
617
-		$appList = [];
618
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
619
-		$urlGenerator = \OC::$server->getURLGenerator();
620
-		$supportedApps = $this->getSupportedApps();
621
-
622
-		foreach ($installedApps as $app) {
623
-			if (array_search($app, $blacklist) === false) {
624
-				$info = OC_App::getAppInfo($app, false, $langCode);
625
-				if (!is_array($info)) {
626
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
627
-					continue;
628
-				}
629
-
630
-				if (!isset($info['name'])) {
631
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
632
-					continue;
633
-				}
634
-
635
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
636
-				$info['groups'] = null;
637
-				if ($enabled === 'yes') {
638
-					$active = true;
639
-				} elseif ($enabled === 'no') {
640
-					$active = false;
641
-				} else {
642
-					$active = true;
643
-					$info['groups'] = $enabled;
644
-				}
645
-
646
-				$info['active'] = $active;
647
-
648
-				if ($appManager->isShipped($app)) {
649
-					$info['internal'] = true;
650
-					$info['level'] = self::officialApp;
651
-					$info['removable'] = false;
652
-				} else {
653
-					$info['internal'] = false;
654
-					$info['removable'] = true;
655
-				}
656
-
657
-				if (in_array($app, $supportedApps)) {
658
-					$info['level'] = self::supportedApp;
659
-				}
660
-
661
-				$appPath = self::getAppPath($app);
662
-				if ($appPath !== false) {
663
-					$appIcon = $appPath . '/img/' . $app . '.svg';
664
-					if (file_exists($appIcon)) {
665
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
666
-						$info['previewAsIcon'] = true;
667
-					} else {
668
-						$appIcon = $appPath . '/img/app.svg';
669
-						if (file_exists($appIcon)) {
670
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
671
-							$info['previewAsIcon'] = true;
672
-						}
673
-					}
674
-				}
675
-				// fix documentation
676
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
677
-					foreach ($info['documentation'] as $key => $url) {
678
-						// If it is not an absolute URL we assume it is a key
679
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
680
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
681
-							$url = $urlGenerator->linkToDocs($url);
682
-						}
683
-
684
-						$info['documentation'][$key] = $url;
685
-					}
686
-				}
687
-
688
-				$info['version'] = OC_App::getAppVersion($app);
689
-				$appList[] = $info;
690
-			}
691
-		}
692
-
693
-		return $appList;
694
-	}
695
-
696
-	public static function shouldUpgrade(string $app): bool {
697
-		$versions = self::getAppVersions();
698
-		$currentVersion = OC_App::getAppVersion($app);
699
-		if ($currentVersion && isset($versions[$app])) {
700
-			$installedVersion = $versions[$app];
701
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
702
-				return true;
703
-			}
704
-		}
705
-		return false;
706
-	}
707
-
708
-	/**
709
-	 * Adjust the number of version parts of $version1 to match
710
-	 * the number of version parts of $version2.
711
-	 *
712
-	 * @param string $version1 version to adjust
713
-	 * @param string $version2 version to take the number of parts from
714
-	 * @return string shortened $version1
715
-	 */
716
-	private static function adjustVersionParts(string $version1, string $version2): string {
717
-		$version1 = explode('.', $version1);
718
-		$version2 = explode('.', $version2);
719
-		// reduce $version1 to match the number of parts in $version2
720
-		while (count($version1) > count($version2)) {
721
-			array_pop($version1);
722
-		}
723
-		// if $version1 does not have enough parts, add some
724
-		while (count($version1) < count($version2)) {
725
-			$version1[] = '0';
726
-		}
727
-		return implode('.', $version1);
728
-	}
729
-
730
-	/**
731
-	 * Check whether the current ownCloud version matches the given
732
-	 * application's version requirements.
733
-	 *
734
-	 * The comparison is made based on the number of parts that the
735
-	 * app info version has. For example for ownCloud 6.0.3 if the
736
-	 * app info version is expecting version 6.0, the comparison is
737
-	 * made on the first two parts of the ownCloud version.
738
-	 * This means that it's possible to specify "requiremin" => 6
739
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
740
-	 *
741
-	 * @param string $ocVersion ownCloud version to check against
742
-	 * @param array $appInfo app info (from xml)
743
-	 *
744
-	 * @return boolean true if compatible, otherwise false
745
-	 */
746
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
747
-		$requireMin = '';
748
-		$requireMax = '';
749
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
750
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
751
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
752
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
753
-		} elseif (isset($appInfo['requiremin'])) {
754
-			$requireMin = $appInfo['requiremin'];
755
-		} elseif (isset($appInfo['require'])) {
756
-			$requireMin = $appInfo['require'];
757
-		}
758
-
759
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
760
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
761
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
762
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
763
-		} elseif (isset($appInfo['requiremax'])) {
764
-			$requireMax = $appInfo['requiremax'];
765
-		}
766
-
767
-		if (!empty($requireMin)
768
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
769
-		) {
770
-			return false;
771
-		}
772
-
773
-		if (!$ignoreMax && !empty($requireMax)
774
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
775
-		) {
776
-			return false;
777
-		}
778
-
779
-		return true;
780
-	}
781
-
782
-	/**
783
-	 * get the installed version of all apps
784
-	 */
785
-	public static function getAppVersions() {
786
-		static $versions;
787
-
788
-		if (!$versions) {
789
-			$appConfig = \OC::$server->getAppConfig();
790
-			$versions = $appConfig->getValues(false, 'installed_version');
791
-		}
792
-		return $versions;
793
-	}
794
-
795
-	/**
796
-	 * update the database for the app and call the update script
797
-	 *
798
-	 * @param string $appId
799
-	 * @return bool
800
-	 */
801
-	public static function updateApp(string $appId): bool {
802
-		// for apps distributed with core, we refresh app path in case the downloaded version
803
-		// have been installed in custom apps and not in the default path
804
-		$appPath = self::getAppPath($appId, true);
805
-		if ($appPath === false) {
806
-			return false;
807
-		}
808
-
809
-		if (is_file($appPath . '/appinfo/database.xml')) {
810
-			\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
811
-			return false;
812
-		}
813
-
814
-		\OC::$server->getAppManager()->clearAppsCache();
815
-		$l = \OC::$server->getL10N('core');
816
-		$appData = self::getAppInfo($appId, false, $l->getLanguageCode());
817
-
818
-		$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
819
-		$ignoreMax = in_array($appId, $ignoreMaxApps, true);
820
-		\OC_App::checkAppDependencies(
821
-			\OC::$server->getConfig(),
822
-			$l,
823
-			$appData,
824
-			$ignoreMax
825
-		);
826
-
827
-		self::registerAutoloading($appId, $appPath, true);
828
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
829
-
830
-		$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
831
-		$ms->migrate();
832
-
833
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
834
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
835
-		// update appversion in app manager
836
-		\OC::$server->getAppManager()->clearAppsCache();
837
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
838
-
839
-		self::setupBackgroundJobs($appData['background-jobs']);
840
-
841
-		//set remote/public handlers
842
-		if (array_key_exists('ocsid', $appData)) {
843
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
844
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
845
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
846
-		}
847
-		foreach ($appData['remote'] as $name => $path) {
848
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
849
-		}
850
-		foreach ($appData['public'] as $name => $path) {
851
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
852
-		}
853
-
854
-		self::setAppTypes($appId);
855
-
856
-		$version = \OC_App::getAppVersion($appId);
857
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
858
-
859
-		\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
860
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
861
-			ManagerEvent::EVENT_APP_UPDATE, $appId
862
-		));
863
-
864
-		return true;
865
-	}
866
-
867
-	/**
868
-	 * @param string $appId
869
-	 * @param string[] $steps
870
-	 * @throws \OC\NeedsUpdateException
871
-	 */
872
-	public static function executeRepairSteps(string $appId, array $steps) {
873
-		if (empty($steps)) {
874
-			return;
875
-		}
876
-		// load the app
877
-		self::loadApp($appId);
878
-
879
-		$dispatcher = \OC::$server->get(IEventDispatcher::class);
880
-
881
-		// load the steps
882
-		$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
883
-		foreach ($steps as $step) {
884
-			try {
885
-				$r->addStep($step);
886
-			} catch (Exception $ex) {
887
-				$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
888
-				\OC::$server->getLogger()->logException($ex);
889
-			}
890
-		}
891
-		// run the steps
892
-		$r->run();
893
-	}
894
-
895
-	public static function setupBackgroundJobs(array $jobs) {
896
-		$queue = \OC::$server->getJobList();
897
-		foreach ($jobs as $job) {
898
-			$queue->add($job);
899
-		}
900
-	}
901
-
902
-	/**
903
-	 * @param string $appId
904
-	 * @param string[] $steps
905
-	 */
906
-	private static function setupLiveMigrations(string $appId, array $steps) {
907
-		$queue = \OC::$server->getJobList();
908
-		foreach ($steps as $step) {
909
-			$queue->add('OC\Migration\BackgroundRepair', [
910
-				'app' => $appId,
911
-				'step' => $step]);
912
-		}
913
-	}
914
-
915
-	/**
916
-	 * @param string $appId
917
-	 * @return \OC\Files\View|false
918
-	 */
919
-	public static function getStorage(string $appId) {
920
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
921
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
922
-				$view = new \OC\Files\View('/' . OC_User::getUser());
923
-				if (!$view->file_exists($appId)) {
924
-					$view->mkdir($appId);
925
-				}
926
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
927
-			} else {
928
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
929
-				return false;
930
-			}
931
-		} else {
932
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
933
-			return false;
934
-		}
935
-	}
936
-
937
-	protected static function findBestL10NOption(array $options, string $lang): string {
938
-		// only a single option
939
-		if (isset($options['@value'])) {
940
-			return $options['@value'];
941
-		}
942
-
943
-		$fallback = $similarLangFallback = $englishFallback = false;
944
-
945
-		$lang = strtolower($lang);
946
-		$similarLang = $lang;
947
-		if (strpos($similarLang, '_')) {
948
-			// For "de_DE" we want to find "de" and the other way around
949
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
950
-		}
951
-
952
-		foreach ($options as $option) {
953
-			if (is_array($option)) {
954
-				if ($fallback === false) {
955
-					$fallback = $option['@value'];
956
-				}
957
-
958
-				if (!isset($option['@attributes']['lang'])) {
959
-					continue;
960
-				}
961
-
962
-				$attributeLang = strtolower($option['@attributes']['lang']);
963
-				if ($attributeLang === $lang) {
964
-					return $option['@value'];
965
-				}
966
-
967
-				if ($attributeLang === $similarLang) {
968
-					$similarLangFallback = $option['@value'];
969
-				} elseif (strpos($attributeLang, $similarLang . '_') === 0) {
970
-					if ($similarLangFallback === false) {
971
-						$similarLangFallback = $option['@value'];
972
-					}
973
-				}
974
-			} else {
975
-				$englishFallback = $option;
976
-			}
977
-		}
978
-
979
-		if ($similarLangFallback !== false) {
980
-			return $similarLangFallback;
981
-		} elseif ($englishFallback !== false) {
982
-			return $englishFallback;
983
-		}
984
-		return (string) $fallback;
985
-	}
986
-
987
-	/**
988
-	 * parses the app data array and enhanced the 'description' value
989
-	 *
990
-	 * @param array $data the app data
991
-	 * @param string $lang
992
-	 * @return array improved app data
993
-	 */
994
-	public static function parseAppInfo(array $data, $lang = null): array {
995
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
996
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
997
-		}
998
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
999
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1000
-		}
1001
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1002
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1003
-		} elseif (isset($data['description']) && is_string($data['description'])) {
1004
-			$data['description'] = trim($data['description']);
1005
-		} else {
1006
-			$data['description'] = '';
1007
-		}
1008
-
1009
-		return $data;
1010
-	}
1011
-
1012
-	/**
1013
-	 * @param \OCP\IConfig $config
1014
-	 * @param \OCP\IL10N $l
1015
-	 * @param array $info
1016
-	 * @throws \Exception
1017
-	 */
1018
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1019
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1020
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1021
-		if (!empty($missing)) {
1022
-			$missingMsg = implode(PHP_EOL, $missing);
1023
-			throw new \Exception(
1024
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1025
-					[$info['name'], $missingMsg]
1026
-				)
1027
-			);
1028
-		}
1029
-	}
76
+    private static $adminForms = [];
77
+    private static $personalForms = [];
78
+    private static $altLogin = [];
79
+    private static $alreadyRegistered = [];
80
+    public const supportedApp = 300;
81
+    public const officialApp = 200;
82
+
83
+    /**
84
+     * clean the appId
85
+     *
86
+     * @psalm-taint-escape file
87
+     * @psalm-taint-escape include
88
+     * @psalm-taint-escape html
89
+     * @psalm-taint-escape has_quotes
90
+     *
91
+     * @param string $app AppId that needs to be cleaned
92
+     * @return string
93
+     */
94
+    public static function cleanAppId(string $app): string {
95
+        return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
96
+    }
97
+
98
+    /**
99
+     * Check if an app is loaded
100
+     *
101
+     * @param string $app
102
+     * @return bool
103
+     * @deprecated 27.0.0 use IAppManager::isAppLoaded
104
+     */
105
+    public static function isAppLoaded(string $app): bool {
106
+        return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
107
+    }
108
+
109
+    /**
110
+     * loads all apps
111
+     *
112
+     * @param string[] $types
113
+     * @return bool
114
+     *
115
+     * This function walks through the ownCloud directory and loads all apps
116
+     * it can find. A directory contains an app if the file /appinfo/info.xml
117
+     * exists.
118
+     *
119
+     * if $types is set to non-empty array, only apps of those types will be loaded
120
+     */
121
+    public static function loadApps(array $types = []): bool {
122
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
123
+            // This should be done before calling this method so that appmanager can be used
124
+            return false;
125
+        }
126
+        return \OC::$server->get(IAppManager::class)->loadApps($types);
127
+    }
128
+
129
+    /**
130
+     * load a single app
131
+     *
132
+     * @param string $app
133
+     * @throws Exception
134
+     * @deprecated 27.0.0 use IAppManager::loadApp
135
+     */
136
+    public static function loadApp(string $app): void {
137
+        \OC::$server->get(IAppManager::class)->loadApp($app);
138
+    }
139
+
140
+    /**
141
+     * @internal
142
+     * @param string $app
143
+     * @param string $path
144
+     * @param bool $force
145
+     */
146
+    public static function registerAutoloading(string $app, string $path, bool $force = false) {
147
+        $key = $app . '-' . $path;
148
+        if (!$force && isset(self::$alreadyRegistered[$key])) {
149
+            return;
150
+        }
151
+
152
+        self::$alreadyRegistered[$key] = true;
153
+
154
+        // Register on PSR-4 composer autoloader
155
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
156
+        \OC::$server->registerNamespace($app, $appNamespace);
157
+
158
+        if (file_exists($path . '/composer/autoload.php')) {
159
+            require_once $path . '/composer/autoload.php';
160
+        } else {
161
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
162
+        }
163
+
164
+        // Register Test namespace only when testing
165
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
166
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
167
+        }
168
+    }
169
+
170
+    /**
171
+     * check if an app is of a specific type
172
+     *
173
+     * @param string $app
174
+     * @param array $types
175
+     * @return bool
176
+     * @deprecated 27.0.0 use IAppManager::isType
177
+     */
178
+    public static function isType(string $app, array $types): bool {
179
+        return \OC::$server->get(IAppManager::class)->isType($app, $types);
180
+    }
181
+
182
+    /**
183
+     * read app types from info.xml and cache them in the database
184
+     */
185
+    public static function setAppTypes(string $app) {
186
+        $appManager = \OC::$server->getAppManager();
187
+        $appData = $appManager->getAppInfo($app);
188
+        if (!is_array($appData)) {
189
+            return;
190
+        }
191
+
192
+        if (isset($appData['types'])) {
193
+            $appTypes = implode(',', $appData['types']);
194
+        } else {
195
+            $appTypes = '';
196
+            $appData['types'] = [];
197
+        }
198
+
199
+        $config = \OC::$server->getConfig();
200
+        $config->setAppValue($app, 'types', $appTypes);
201
+
202
+        if ($appManager->hasProtectedAppType($appData['types'])) {
203
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
204
+            if ($enabled !== 'yes' && $enabled !== 'no') {
205
+                $config->setAppValue($app, 'enabled', 'yes');
206
+            }
207
+        }
208
+    }
209
+
210
+    /**
211
+     * Returns apps enabled for the current user.
212
+     *
213
+     * @param bool $forceRefresh whether to refresh the cache
214
+     * @param bool $all whether to return apps for all users, not only the
215
+     * currently logged in one
216
+     * @return string[]
217
+     */
218
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
219
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
220
+            return [];
221
+        }
222
+        // in incognito mode or when logged out, $user will be false,
223
+        // which is also the case during an upgrade
224
+        $appManager = \OC::$server->getAppManager();
225
+        if ($all) {
226
+            $user = null;
227
+        } else {
228
+            $user = \OC::$server->getUserSession()->getUser();
229
+        }
230
+
231
+        if (is_null($user)) {
232
+            $apps = $appManager->getInstalledApps();
233
+        } else {
234
+            $apps = $appManager->getEnabledAppsForUser($user);
235
+        }
236
+        $apps = array_filter($apps, function ($app) {
237
+            return $app !== 'files';//we add this manually
238
+        });
239
+        sort($apps);
240
+        array_unshift($apps, 'files');
241
+        return $apps;
242
+    }
243
+
244
+    /**
245
+     * enables an app
246
+     *
247
+     * @param string $appId
248
+     * @param array $groups (optional) when set, only these groups will have access to the app
249
+     * @throws \Exception
250
+     * @return void
251
+     *
252
+     * This function set an app as enabled in appconfig.
253
+     */
254
+    public function enable(string $appId,
255
+                            array $groups = []) {
256
+        // Check if app is already downloaded
257
+        /** @var Installer $installer */
258
+        $installer = \OC::$server->query(Installer::class);
259
+        $isDownloaded = $installer->isDownloaded($appId);
260
+
261
+        if (!$isDownloaded) {
262
+            $installer->downloadApp($appId);
263
+        }
264
+
265
+        $installer->installApp($appId);
266
+
267
+        $appManager = \OC::$server->getAppManager();
268
+        if ($groups !== []) {
269
+            $groupManager = \OC::$server->getGroupManager();
270
+            $groupsList = [];
271
+            foreach ($groups as $group) {
272
+                $groupItem = $groupManager->get($group);
273
+                if ($groupItem instanceof \OCP\IGroup) {
274
+                    $groupsList[] = $groupManager->get($group);
275
+                }
276
+            }
277
+            $appManager->enableAppForGroups($appId, $groupsList);
278
+        } else {
279
+            $appManager->enableApp($appId);
280
+        }
281
+    }
282
+
283
+    /**
284
+     * Get the path where to install apps
285
+     *
286
+     * @return string|false
287
+     */
288
+    public static function getInstallPath() {
289
+        foreach (OC::$APPSROOTS as $dir) {
290
+            if (isset($dir['writable']) && $dir['writable'] === true) {
291
+                return $dir['path'];
292
+            }
293
+        }
294
+
295
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
296
+        return null;
297
+    }
298
+
299
+
300
+    /**
301
+     * search for an app in all app-directories
302
+     *
303
+     * @param string $appId
304
+     * @param bool $ignoreCache ignore cache and rebuild it
305
+     * @return false|string
306
+     */
307
+    public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
308
+        $sanitizedAppId = self::cleanAppId($appId);
309
+        if ($sanitizedAppId !== $appId) {
310
+            return false;
311
+        }
312
+        static $app_dir = [];
313
+
314
+        if (isset($app_dir[$appId]) && !$ignoreCache) {
315
+            return $app_dir[$appId];
316
+        }
317
+
318
+        $possibleApps = [];
319
+        foreach (OC::$APPSROOTS as $dir) {
320
+            if (file_exists($dir['path'] . '/' . $appId)) {
321
+                $possibleApps[] = $dir;
322
+            }
323
+        }
324
+
325
+        if (empty($possibleApps)) {
326
+            return false;
327
+        } elseif (count($possibleApps) === 1) {
328
+            $dir = array_shift($possibleApps);
329
+            $app_dir[$appId] = $dir;
330
+            return $dir;
331
+        } else {
332
+            $versionToLoad = [];
333
+            foreach ($possibleApps as $possibleApp) {
334
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
335
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
336
+                    $versionToLoad = [
337
+                        'dir' => $possibleApp,
338
+                        'version' => $version,
339
+                    ];
340
+                }
341
+            }
342
+            $app_dir[$appId] = $versionToLoad['dir'];
343
+            return $versionToLoad['dir'];
344
+            //TODO - write test
345
+        }
346
+    }
347
+
348
+    /**
349
+     * Get the directory for the given app.
350
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
351
+     *
352
+     * @psalm-taint-specialize
353
+     *
354
+     * @param string $appId
355
+     * @param bool $refreshAppPath should be set to true only during install/upgrade
356
+     * @return string|false
357
+     * @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
358
+     */
359
+    public static function getAppPath(string $appId, bool $refreshAppPath = false) {
360
+        if ($appId === null || trim($appId) === '') {
361
+            return false;
362
+        }
363
+
364
+        if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
365
+            return $dir['path'] . '/' . $appId;
366
+        }
367
+        return false;
368
+    }
369
+
370
+    /**
371
+     * Get the path for the given app on the access
372
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
373
+     *
374
+     * @param string $appId
375
+     * @return string|false
376
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
377
+     */
378
+    public static function getAppWebPath(string $appId) {
379
+        if (($dir = self::findAppInDirectories($appId)) != false) {
380
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
381
+        }
382
+        return false;
383
+    }
384
+
385
+    /**
386
+     * get the last version of the app from appinfo/info.xml
387
+     *
388
+     * @param string $appId
389
+     * @param bool $useCache
390
+     * @return string
391
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
392
+     */
393
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
394
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
395
+    }
396
+
397
+    /**
398
+     * get app's version based on it's path
399
+     *
400
+     * @param string $path
401
+     * @return string
402
+     */
403
+    public static function getAppVersionByPath(string $path): string {
404
+        $infoFile = $path . '/appinfo/info.xml';
405
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
406
+        return isset($appData['version']) ? $appData['version'] : '';
407
+    }
408
+
409
+
410
+    /**
411
+     * Read all app metadata from the info.xml file
412
+     *
413
+     * @param string $appId id of the app or the path of the info.xml file
414
+     * @param bool $path
415
+     * @param string $lang
416
+     * @return array|null
417
+     * @note all data is read from info.xml, not just pre-defined fields
418
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
419
+     */
420
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
421
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
422
+    }
423
+
424
+    /**
425
+     * Returns the navigation
426
+     *
427
+     * @return array
428
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
429
+     *
430
+     * This function returns an array containing all entries added. The
431
+     * entries are sorted by the key 'order' ascending. Additional to the keys
432
+     * given for each app the following keys exist:
433
+     *   - active: boolean, signals if the user is on this navigation entry
434
+     */
435
+    public static function getNavigation(): array {
436
+        return OC::$server->getNavigationManager()->getAll();
437
+    }
438
+
439
+    /**
440
+     * Returns the Settings Navigation
441
+     *
442
+     * @return string[]
443
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
444
+     *
445
+     * This function returns an array containing all settings pages added. The
446
+     * entries are sorted by the key 'order' ascending.
447
+     */
448
+    public static function getSettingsNavigation(): array {
449
+        return OC::$server->getNavigationManager()->getAll('settings');
450
+    }
451
+
452
+    /**
453
+     * get the id of loaded app
454
+     *
455
+     * @return string
456
+     */
457
+    public static function getCurrentApp(): string {
458
+        if (\OC::$CLI) {
459
+            return '';
460
+        }
461
+
462
+        $request = \OC::$server->getRequest();
463
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
464
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
465
+        if (empty($topFolder)) {
466
+            try {
467
+                $path_info = $request->getPathInfo();
468
+            } catch (Exception $e) {
469
+                // Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
470
+                \OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
471
+                return '';
472
+            }
473
+            if ($path_info) {
474
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
475
+            }
476
+        }
477
+        if ($topFolder == 'apps') {
478
+            $length = strlen($topFolder);
479
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
480
+        } else {
481
+            return $topFolder;
482
+        }
483
+    }
484
+
485
+    /**
486
+     * @param string $type
487
+     * @return array
488
+     */
489
+    public static function getForms(string $type): array {
490
+        $forms = [];
491
+        switch ($type) {
492
+            case 'admin':
493
+                $source = self::$adminForms;
494
+                break;
495
+            case 'personal':
496
+                $source = self::$personalForms;
497
+                break;
498
+            default:
499
+                return [];
500
+        }
501
+        foreach ($source as $form) {
502
+            $forms[] = include $form;
503
+        }
504
+        return $forms;
505
+    }
506
+
507
+    /**
508
+     * @param array $entry
509
+     * @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
510
+     */
511
+    public static function registerLogIn(array $entry) {
512
+        \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');
513
+        self::$altLogin[] = $entry;
514
+    }
515
+
516
+    /**
517
+     * @return array
518
+     */
519
+    public static function getAlternativeLogIns(): array {
520
+        /** @var Coordinator $bootstrapCoordinator */
521
+        $bootstrapCoordinator = \OC::$server->query(Coordinator::class);
522
+
523
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
524
+            if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
525
+                \OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
526
+                    'option' => $registration->getService(),
527
+                    'interface' => IAlternativeLogin::class,
528
+                    'app' => $registration->getAppId(),
529
+                ]);
530
+                continue;
531
+            }
532
+
533
+            try {
534
+                /** @var IAlternativeLogin $provider */
535
+                $provider = \OC::$server->query($registration->getService());
536
+            } catch (QueryException $e) {
537
+                \OC::$server->getLogger()->logException($e, [
538
+                    'message' => 'Alternative login option {option} can not be initialised.',
539
+                    'option' => $registration->getService(),
540
+                    'app' => $registration->getAppId(),
541
+                ]);
542
+            }
543
+
544
+            try {
545
+                $provider->load();
546
+
547
+                self::$altLogin[] = [
548
+                    'name' => $provider->getLabel(),
549
+                    'href' => $provider->getLink(),
550
+                    'class' => $provider->getClass(),
551
+                ];
552
+            } catch (Throwable $e) {
553
+                \OC::$server->getLogger()->logException($e, [
554
+                    'message' => 'Alternative login option {option} had an error while loading.',
555
+                    'option' => $registration->getService(),
556
+                    'app' => $registration->getAppId(),
557
+                ]);
558
+            }
559
+        }
560
+
561
+        return self::$altLogin;
562
+    }
563
+
564
+    /**
565
+     * get a list of all apps in the apps folder
566
+     *
567
+     * @return string[] an array of app names (string IDs)
568
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
569
+     */
570
+    public static function getAllApps(): array {
571
+        $apps = [];
572
+
573
+        foreach (OC::$APPSROOTS as $apps_dir) {
574
+            if (!is_readable($apps_dir['path'])) {
575
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
576
+                continue;
577
+            }
578
+            $dh = opendir($apps_dir['path']);
579
+
580
+            if (is_resource($dh)) {
581
+                while (($file = readdir($dh)) !== false) {
582
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
583
+                        $apps[] = $file;
584
+                    }
585
+                }
586
+            }
587
+        }
588
+
589
+        $apps = array_unique($apps);
590
+
591
+        return $apps;
592
+    }
593
+
594
+    /**
595
+     * List all supported apps
596
+     *
597
+     * @return array
598
+     */
599
+    public function getSupportedApps(): array {
600
+        /** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
601
+        $subscriptionRegistry = \OC::$server->query(\OCP\Support\Subscription\IRegistry::class);
602
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
603
+        return $supportedApps;
604
+    }
605
+
606
+    /**
607
+     * List all apps, this is used in apps.php
608
+     *
609
+     * @return array
610
+     */
611
+    public function listAllApps(): array {
612
+        $installedApps = OC_App::getAllApps();
613
+
614
+        $appManager = \OC::$server->getAppManager();
615
+        //we don't want to show configuration for these
616
+        $blacklist = $appManager->getAlwaysEnabledApps();
617
+        $appList = [];
618
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
619
+        $urlGenerator = \OC::$server->getURLGenerator();
620
+        $supportedApps = $this->getSupportedApps();
621
+
622
+        foreach ($installedApps as $app) {
623
+            if (array_search($app, $blacklist) === false) {
624
+                $info = OC_App::getAppInfo($app, false, $langCode);
625
+                if (!is_array($info)) {
626
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
627
+                    continue;
628
+                }
629
+
630
+                if (!isset($info['name'])) {
631
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
632
+                    continue;
633
+                }
634
+
635
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
636
+                $info['groups'] = null;
637
+                if ($enabled === 'yes') {
638
+                    $active = true;
639
+                } elseif ($enabled === 'no') {
640
+                    $active = false;
641
+                } else {
642
+                    $active = true;
643
+                    $info['groups'] = $enabled;
644
+                }
645
+
646
+                $info['active'] = $active;
647
+
648
+                if ($appManager->isShipped($app)) {
649
+                    $info['internal'] = true;
650
+                    $info['level'] = self::officialApp;
651
+                    $info['removable'] = false;
652
+                } else {
653
+                    $info['internal'] = false;
654
+                    $info['removable'] = true;
655
+                }
656
+
657
+                if (in_array($app, $supportedApps)) {
658
+                    $info['level'] = self::supportedApp;
659
+                }
660
+
661
+                $appPath = self::getAppPath($app);
662
+                if ($appPath !== false) {
663
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
664
+                    if (file_exists($appIcon)) {
665
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
666
+                        $info['previewAsIcon'] = true;
667
+                    } else {
668
+                        $appIcon = $appPath . '/img/app.svg';
669
+                        if (file_exists($appIcon)) {
670
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
671
+                            $info['previewAsIcon'] = true;
672
+                        }
673
+                    }
674
+                }
675
+                // fix documentation
676
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
677
+                    foreach ($info['documentation'] as $key => $url) {
678
+                        // If it is not an absolute URL we assume it is a key
679
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
680
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
681
+                            $url = $urlGenerator->linkToDocs($url);
682
+                        }
683
+
684
+                        $info['documentation'][$key] = $url;
685
+                    }
686
+                }
687
+
688
+                $info['version'] = OC_App::getAppVersion($app);
689
+                $appList[] = $info;
690
+            }
691
+        }
692
+
693
+        return $appList;
694
+    }
695
+
696
+    public static function shouldUpgrade(string $app): bool {
697
+        $versions = self::getAppVersions();
698
+        $currentVersion = OC_App::getAppVersion($app);
699
+        if ($currentVersion && isset($versions[$app])) {
700
+            $installedVersion = $versions[$app];
701
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
702
+                return true;
703
+            }
704
+        }
705
+        return false;
706
+    }
707
+
708
+    /**
709
+     * Adjust the number of version parts of $version1 to match
710
+     * the number of version parts of $version2.
711
+     *
712
+     * @param string $version1 version to adjust
713
+     * @param string $version2 version to take the number of parts from
714
+     * @return string shortened $version1
715
+     */
716
+    private static function adjustVersionParts(string $version1, string $version2): string {
717
+        $version1 = explode('.', $version1);
718
+        $version2 = explode('.', $version2);
719
+        // reduce $version1 to match the number of parts in $version2
720
+        while (count($version1) > count($version2)) {
721
+            array_pop($version1);
722
+        }
723
+        // if $version1 does not have enough parts, add some
724
+        while (count($version1) < count($version2)) {
725
+            $version1[] = '0';
726
+        }
727
+        return implode('.', $version1);
728
+    }
729
+
730
+    /**
731
+     * Check whether the current ownCloud version matches the given
732
+     * application's version requirements.
733
+     *
734
+     * The comparison is made based on the number of parts that the
735
+     * app info version has. For example for ownCloud 6.0.3 if the
736
+     * app info version is expecting version 6.0, the comparison is
737
+     * made on the first two parts of the ownCloud version.
738
+     * This means that it's possible to specify "requiremin" => 6
739
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
740
+     *
741
+     * @param string $ocVersion ownCloud version to check against
742
+     * @param array $appInfo app info (from xml)
743
+     *
744
+     * @return boolean true if compatible, otherwise false
745
+     */
746
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
747
+        $requireMin = '';
748
+        $requireMax = '';
749
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
750
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
751
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
752
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
753
+        } elseif (isset($appInfo['requiremin'])) {
754
+            $requireMin = $appInfo['requiremin'];
755
+        } elseif (isset($appInfo['require'])) {
756
+            $requireMin = $appInfo['require'];
757
+        }
758
+
759
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
760
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
761
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
762
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
763
+        } elseif (isset($appInfo['requiremax'])) {
764
+            $requireMax = $appInfo['requiremax'];
765
+        }
766
+
767
+        if (!empty($requireMin)
768
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
769
+        ) {
770
+            return false;
771
+        }
772
+
773
+        if (!$ignoreMax && !empty($requireMax)
774
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
775
+        ) {
776
+            return false;
777
+        }
778
+
779
+        return true;
780
+    }
781
+
782
+    /**
783
+     * get the installed version of all apps
784
+     */
785
+    public static function getAppVersions() {
786
+        static $versions;
787
+
788
+        if (!$versions) {
789
+            $appConfig = \OC::$server->getAppConfig();
790
+            $versions = $appConfig->getValues(false, 'installed_version');
791
+        }
792
+        return $versions;
793
+    }
794
+
795
+    /**
796
+     * update the database for the app and call the update script
797
+     *
798
+     * @param string $appId
799
+     * @return bool
800
+     */
801
+    public static function updateApp(string $appId): bool {
802
+        // for apps distributed with core, we refresh app path in case the downloaded version
803
+        // have been installed in custom apps and not in the default path
804
+        $appPath = self::getAppPath($appId, true);
805
+        if ($appPath === false) {
806
+            return false;
807
+        }
808
+
809
+        if (is_file($appPath . '/appinfo/database.xml')) {
810
+            \OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
811
+            return false;
812
+        }
813
+
814
+        \OC::$server->getAppManager()->clearAppsCache();
815
+        $l = \OC::$server->getL10N('core');
816
+        $appData = self::getAppInfo($appId, false, $l->getLanguageCode());
817
+
818
+        $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
819
+        $ignoreMax = in_array($appId, $ignoreMaxApps, true);
820
+        \OC_App::checkAppDependencies(
821
+            \OC::$server->getConfig(),
822
+            $l,
823
+            $appData,
824
+            $ignoreMax
825
+        );
826
+
827
+        self::registerAutoloading($appId, $appPath, true);
828
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
829
+
830
+        $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
831
+        $ms->migrate();
832
+
833
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
834
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
835
+        // update appversion in app manager
836
+        \OC::$server->getAppManager()->clearAppsCache();
837
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
838
+
839
+        self::setupBackgroundJobs($appData['background-jobs']);
840
+
841
+        //set remote/public handlers
842
+        if (array_key_exists('ocsid', $appData)) {
843
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
844
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
845
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
846
+        }
847
+        foreach ($appData['remote'] as $name => $path) {
848
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
849
+        }
850
+        foreach ($appData['public'] as $name => $path) {
851
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
852
+        }
853
+
854
+        self::setAppTypes($appId);
855
+
856
+        $version = \OC_App::getAppVersion($appId);
857
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
858
+
859
+        \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
860
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
861
+            ManagerEvent::EVENT_APP_UPDATE, $appId
862
+        ));
863
+
864
+        return true;
865
+    }
866
+
867
+    /**
868
+     * @param string $appId
869
+     * @param string[] $steps
870
+     * @throws \OC\NeedsUpdateException
871
+     */
872
+    public static function executeRepairSteps(string $appId, array $steps) {
873
+        if (empty($steps)) {
874
+            return;
875
+        }
876
+        // load the app
877
+        self::loadApp($appId);
878
+
879
+        $dispatcher = \OC::$server->get(IEventDispatcher::class);
880
+
881
+        // load the steps
882
+        $r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
883
+        foreach ($steps as $step) {
884
+            try {
885
+                $r->addStep($step);
886
+            } catch (Exception $ex) {
887
+                $dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
888
+                \OC::$server->getLogger()->logException($ex);
889
+            }
890
+        }
891
+        // run the steps
892
+        $r->run();
893
+    }
894
+
895
+    public static function setupBackgroundJobs(array $jobs) {
896
+        $queue = \OC::$server->getJobList();
897
+        foreach ($jobs as $job) {
898
+            $queue->add($job);
899
+        }
900
+    }
901
+
902
+    /**
903
+     * @param string $appId
904
+     * @param string[] $steps
905
+     */
906
+    private static function setupLiveMigrations(string $appId, array $steps) {
907
+        $queue = \OC::$server->getJobList();
908
+        foreach ($steps as $step) {
909
+            $queue->add('OC\Migration\BackgroundRepair', [
910
+                'app' => $appId,
911
+                'step' => $step]);
912
+        }
913
+    }
914
+
915
+    /**
916
+     * @param string $appId
917
+     * @return \OC\Files\View|false
918
+     */
919
+    public static function getStorage(string $appId) {
920
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
921
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
922
+                $view = new \OC\Files\View('/' . OC_User::getUser());
923
+                if (!$view->file_exists($appId)) {
924
+                    $view->mkdir($appId);
925
+                }
926
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
927
+            } else {
928
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
929
+                return false;
930
+            }
931
+        } else {
932
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
933
+            return false;
934
+        }
935
+    }
936
+
937
+    protected static function findBestL10NOption(array $options, string $lang): string {
938
+        // only a single option
939
+        if (isset($options['@value'])) {
940
+            return $options['@value'];
941
+        }
942
+
943
+        $fallback = $similarLangFallback = $englishFallback = false;
944
+
945
+        $lang = strtolower($lang);
946
+        $similarLang = $lang;
947
+        if (strpos($similarLang, '_')) {
948
+            // For "de_DE" we want to find "de" and the other way around
949
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
950
+        }
951
+
952
+        foreach ($options as $option) {
953
+            if (is_array($option)) {
954
+                if ($fallback === false) {
955
+                    $fallback = $option['@value'];
956
+                }
957
+
958
+                if (!isset($option['@attributes']['lang'])) {
959
+                    continue;
960
+                }
961
+
962
+                $attributeLang = strtolower($option['@attributes']['lang']);
963
+                if ($attributeLang === $lang) {
964
+                    return $option['@value'];
965
+                }
966
+
967
+                if ($attributeLang === $similarLang) {
968
+                    $similarLangFallback = $option['@value'];
969
+                } elseif (strpos($attributeLang, $similarLang . '_') === 0) {
970
+                    if ($similarLangFallback === false) {
971
+                        $similarLangFallback = $option['@value'];
972
+                    }
973
+                }
974
+            } else {
975
+                $englishFallback = $option;
976
+            }
977
+        }
978
+
979
+        if ($similarLangFallback !== false) {
980
+            return $similarLangFallback;
981
+        } elseif ($englishFallback !== false) {
982
+            return $englishFallback;
983
+        }
984
+        return (string) $fallback;
985
+    }
986
+
987
+    /**
988
+     * parses the app data array and enhanced the 'description' value
989
+     *
990
+     * @param array $data the app data
991
+     * @param string $lang
992
+     * @return array improved app data
993
+     */
994
+    public static function parseAppInfo(array $data, $lang = null): array {
995
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
996
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
997
+        }
998
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
999
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1000
+        }
1001
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1002
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1003
+        } elseif (isset($data['description']) && is_string($data['description'])) {
1004
+            $data['description'] = trim($data['description']);
1005
+        } else {
1006
+            $data['description'] = '';
1007
+        }
1008
+
1009
+        return $data;
1010
+    }
1011
+
1012
+    /**
1013
+     * @param \OCP\IConfig $config
1014
+     * @param \OCP\IL10N $l
1015
+     * @param array $info
1016
+     * @throws \Exception
1017
+     */
1018
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
1019
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1020
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
1021
+        if (!empty($missing)) {
1022
+            $missingMsg = implode(PHP_EOL, $missing);
1023
+            throw new \Exception(
1024
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
1025
+                    [$info['name'], $missingMsg]
1026
+                )
1027
+            );
1028
+        }
1029
+    }
1030 1030
 }
Please login to merge, or discard this patch.