Completed
Pull Request — master (#9565)
by Julius
64:13 queued 44:35
created
settings/routes.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -38,55 +38,55 @@
 block discarded – undo
38 38
 
39 39
 $application = new Application();
40 40
 $application->registerRoutes($this, [
41
-	'resources' => [
42
-		'AuthSettings' => ['url' => '/settings/personal/authtokens'],
43
-	],
44
-	'routes' => [
45
-		['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
46
-		['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
47
-		['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
48
-		['name' => 'Encryption#startMigration', 'url' => '/settings/admin/startmigration', 'verb' => 'POST'],
41
+    'resources' => [
42
+        'AuthSettings' => ['url' => '/settings/personal/authtokens'],
43
+    ],
44
+    'routes' => [
45
+        ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
46
+        ['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
47
+        ['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
48
+        ['name' => 'Encryption#startMigration', 'url' => '/settings/admin/startmigration', 'verb' => 'POST'],
49 49
 
50
-		['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
51
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
52
-		['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
53
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET'],
54
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST'],
55
-		['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST'],
56
-		['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET'],
57
-		['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST'],
58
-		['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET'],
59
-		['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET'],
60
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => '']],
61
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => '']],
50
+        ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
51
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
52
+        ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
53
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET'],
54
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST'],
55
+        ['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST'],
56
+        ['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET'],
57
+        ['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST'],
58
+        ['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET'],
59
+        ['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET'],
60
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => '']],
61
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => '']],
62 62
 
63
-		['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
64
-		['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
65
-		['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
66
-		['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],
67
-		['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET'],
68
-		['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET'],
69
-		['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
70
-		['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
71
-		['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
72
-		['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
73
-		['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET'],
74
-		['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET'],
75
-		['name' => 'Certificate#addPersonalRootCertificate', 'url' => '/settings/personal/certificate', 'verb' => 'POST'],
76
-		['name' => 'Certificate#removePersonalRootCertificate', 'url' => '/settings/personal/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
77
-		['name' => 'Certificate#addSystemRootCertificate', 'url' => '/settings/admin/certificate', 'verb' => 'POST'],
78
-		['name' => 'Certificate#removeSystemRootCertificate', 'url' => '/settings/admin/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
79
-		['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info']],
80
-		['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server']],
81
-		['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'],
82
-		['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'],
83
-		['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST']
84
-	]
63
+        ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
64
+        ['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
65
+        ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
66
+        ['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],
67
+        ['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET'],
68
+        ['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET'],
69
+        ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
70
+        ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
71
+        ['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
72
+        ['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
73
+        ['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET'],
74
+        ['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET'],
75
+        ['name' => 'Certificate#addPersonalRootCertificate', 'url' => '/settings/personal/certificate', 'verb' => 'POST'],
76
+        ['name' => 'Certificate#removePersonalRootCertificate', 'url' => '/settings/personal/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
77
+        ['name' => 'Certificate#addSystemRootCertificate', 'url' => '/settings/admin/certificate', 'verb' => 'POST'],
78
+        ['name' => 'Certificate#removeSystemRootCertificate', 'url' => '/settings/admin/certificate/{certificateIdentifier}', 'verb' => 'DELETE'],
79
+        ['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info']],
80
+        ['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server']],
81
+        ['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'],
82
+        ['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'],
83
+        ['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST']
84
+    ]
85 85
 ]);
86 86
 
87 87
 /** @var $this \OCP\Route\IRouter */
88 88
 
89 89
 // Settings pages
90 90
 $this->create('settings_help', '/settings/help')
91
-	->actionInclude('settings/help.php');
91
+    ->actionInclude('settings/help.php');
92 92
 
Please login to merge, or discard this patch.
lib/private/legacy/app.php 1 patch
Indentation   +1036 added lines, -1036 removed lines patch added patch discarded remove patch
@@ -64,1040 +64,1040 @@
 block discarded – undo
64 64
  * upgrading and removing apps.
65 65
  */
66 66
 class OC_App {
67
-	static private $adminForms = [];
68
-	static private $personalForms = [];
69
-	static private $appTypes = [];
70
-	static private $loadedApps = [];
71
-	static private $altLogin = [];
72
-	static private $alreadyRegistered = [];
73
-	const officialApp = 200;
74
-
75
-	/**
76
-	 * clean the appId
77
-	 *
78
-	 * @param string $app AppId that needs to be cleaned
79
-	 * @return string
80
-	 */
81
-	public static function cleanAppId(string $app): string {
82
-		return str_replace(array('\0', '/', '\\', '..'), '', $app);
83
-	}
84
-
85
-	/**
86
-	 * Check if an app is loaded
87
-	 *
88
-	 * @param string $app
89
-	 * @return bool
90
-	 */
91
-	public static function isAppLoaded(string $app): bool {
92
-		return in_array($app, self::$loadedApps, true);
93
-	}
94
-
95
-	/**
96
-	 * loads all apps
97
-	 *
98
-	 * @param string[] $types
99
-	 * @return bool
100
-	 *
101
-	 * This function walks through the ownCloud directory and loads all apps
102
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
103
-	 * exists.
104
-	 *
105
-	 * if $types is set to non-empty array, only apps of those types will be loaded
106
-	 */
107
-	public static function loadApps(array $types = []): bool {
108
-		if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
109
-			return false;
110
-		}
111
-		// Load the enabled apps here
112
-		$apps = self::getEnabledApps();
113
-
114
-		// Add each apps' folder as allowed class path
115
-		foreach($apps as $app) {
116
-			$path = self::getAppPath($app);
117
-			if($path !== false) {
118
-				self::registerAutoloading($app, $path);
119
-			}
120
-		}
121
-
122
-		// prevent app.php from printing output
123
-		ob_start();
124
-		foreach ($apps as $app) {
125
-			if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
126
-				self::loadApp($app);
127
-			}
128
-		}
129
-		ob_end_clean();
130
-
131
-		return true;
132
-	}
133
-
134
-	/**
135
-	 * load a single app
136
-	 *
137
-	 * @param string $app
138
-	 * @throws Exception
139
-	 */
140
-	public static function loadApp(string $app) {
141
-		self::$loadedApps[] = $app;
142
-		$appPath = self::getAppPath($app);
143
-		if($appPath === false) {
144
-			return;
145
-		}
146
-
147
-		// in case someone calls loadApp() directly
148
-		self::registerAutoloading($app, $appPath);
149
-
150
-		if (is_file($appPath . '/appinfo/app.php')) {
151
-			\OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
152
-			try {
153
-				self::requireAppFile($app);
154
-			} catch (Error $ex) {
155
-				\OC::$server->getLogger()->logException($ex);
156
-				if (!\OC::$server->getAppManager()->isShipped($app)) {
157
-					// Only disable apps which are not shipped
158
-					\OC::$server->getAppManager()->disableApp($app);
159
-				}
160
-			}
161
-			\OC::$server->getEventLogger()->end('load_app_' . $app);
162
-		}
163
-
164
-		$info = self::getAppInfo($app);
165
-		if (!empty($info['activity']['filters'])) {
166
-			foreach ($info['activity']['filters'] as $filter) {
167
-				\OC::$server->getActivityManager()->registerFilter($filter);
168
-			}
169
-		}
170
-		if (!empty($info['activity']['settings'])) {
171
-			foreach ($info['activity']['settings'] as $setting) {
172
-				\OC::$server->getActivityManager()->registerSetting($setting);
173
-			}
174
-		}
175
-		if (!empty($info['activity']['providers'])) {
176
-			foreach ($info['activity']['providers'] as $provider) {
177
-				\OC::$server->getActivityManager()->registerProvider($provider);
178
-			}
179
-		}
180
-
181
-		if (!empty($info['settings']['admin'])) {
182
-			foreach ($info['settings']['admin'] as $setting) {
183
-				\OC::$server->getSettingsManager()->registerSetting('admin', $setting);
184
-			}
185
-		}
186
-		if (!empty($info['settings']['admin-section'])) {
187
-			foreach ($info['settings']['admin-section'] as $section) {
188
-				\OC::$server->getSettingsManager()->registerSection('admin', $section);
189
-			}
190
-		}
191
-		if (!empty($info['settings']['personal'])) {
192
-			foreach ($info['settings']['personal'] as $setting) {
193
-				\OC::$server->getSettingsManager()->registerSetting('personal', $setting);
194
-			}
195
-		}
196
-		if (!empty($info['settings']['personal-section'])) {
197
-			foreach ($info['settings']['personal-section'] as $section) {
198
-				\OC::$server->getSettingsManager()->registerSection('personal', $section);
199
-			}
200
-		}
201
-
202
-		if (!empty($info['collaboration']['plugins'])) {
203
-			// deal with one or many plugin entries
204
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
205
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
206
-			foreach ($plugins as $plugin) {
207
-				if($plugin['@attributes']['type'] === 'collaborator-search') {
208
-					$pluginInfo = [
209
-						'shareType' => $plugin['@attributes']['share-type'],
210
-						'class' => $plugin['@value'],
211
-					];
212
-					\OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
213
-				} else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
214
-					\OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
215
-				}
216
-			}
217
-		}
218
-	}
219
-
220
-	/**
221
-	 * @internal
222
-	 * @param string $app
223
-	 * @param string $path
224
-	 */
225
-	public static function registerAutoloading(string $app, string $path) {
226
-		$key = $app . '-' . $path;
227
-		if(isset(self::$alreadyRegistered[$key])) {
228
-			return;
229
-		}
230
-
231
-		self::$alreadyRegistered[$key] = true;
232
-
233
-		// Register on PSR-4 composer autoloader
234
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
235
-		\OC::$server->registerNamespace($app, $appNamespace);
236
-
237
-		if (file_exists($path . '/composer/autoload.php')) {
238
-			require_once $path . '/composer/autoload.php';
239
-		} else {
240
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
241
-			// Register on legacy autoloader
242
-			\OC::$loader->addValidRoot($path);
243
-		}
244
-
245
-		// Register Test namespace only when testing
246
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
247
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
248
-		}
249
-	}
250
-
251
-	/**
252
-	 * Load app.php from the given app
253
-	 *
254
-	 * @param string $app app name
255
-	 * @throws Error
256
-	 */
257
-	private static function requireAppFile(string $app) {
258
-		// encapsulated here to avoid variable scope conflicts
259
-		require_once $app . '/appinfo/app.php';
260
-	}
261
-
262
-	/**
263
-	 * check if an app is of a specific type
264
-	 *
265
-	 * @param string $app
266
-	 * @param array $types
267
-	 * @return bool
268
-	 */
269
-	public static function isType(string $app, array $types): bool {
270
-		$appTypes = self::getAppTypes($app);
271
-		foreach ($types as $type) {
272
-			if (array_search($type, $appTypes) !== false) {
273
-				return true;
274
-			}
275
-		}
276
-		return false;
277
-	}
278
-
279
-	/**
280
-	 * get the types of an app
281
-	 *
282
-	 * @param string $app
283
-	 * @return array
284
-	 */
285
-	private static function getAppTypes(string $app): array {
286
-		//load the cache
287
-		if (count(self::$appTypes) == 0) {
288
-			self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
289
-		}
290
-
291
-		if (isset(self::$appTypes[$app])) {
292
-			return explode(',', self::$appTypes[$app]);
293
-		}
294
-
295
-		return [];
296
-	}
297
-
298
-	/**
299
-	 * read app types from info.xml and cache them in the database
300
-	 */
301
-	public static function setAppTypes(string $app) {
302
-		$appManager = \OC::$server->getAppManager();
303
-		$appData = $appManager->getAppInfo($app);
304
-		if(!is_array($appData)) {
305
-			return;
306
-		}
307
-
308
-		if (isset($appData['types'])) {
309
-			$appTypes = implode(',', $appData['types']);
310
-		} else {
311
-			$appTypes = '';
312
-			$appData['types'] = [];
313
-		}
314
-
315
-		$config = \OC::$server->getConfig();
316
-		$config->setAppValue($app, 'types', $appTypes);
317
-
318
-		if ($appManager->hasProtectedAppType($appData['types'])) {
319
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
320
-			if ($enabled !== 'yes' && $enabled !== 'no') {
321
-				$config->setAppValue($app, 'enabled', 'yes');
322
-			}
323
-		}
324
-	}
325
-
326
-	/**
327
-	 * Returns apps enabled for the current user.
328
-	 *
329
-	 * @param bool $forceRefresh whether to refresh the cache
330
-	 * @param bool $all whether to return apps for all users, not only the
331
-	 * currently logged in one
332
-	 * @return string[]
333
-	 */
334
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
335
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
336
-			return [];
337
-		}
338
-		// in incognito mode or when logged out, $user will be false,
339
-		// which is also the case during an upgrade
340
-		$appManager = \OC::$server->getAppManager();
341
-		if ($all) {
342
-			$user = null;
343
-		} else {
344
-			$user = \OC::$server->getUserSession()->getUser();
345
-		}
346
-
347
-		if (is_null($user)) {
348
-			$apps = $appManager->getInstalledApps();
349
-		} else {
350
-			$apps = $appManager->getEnabledAppsForUser($user);
351
-		}
352
-		$apps = array_filter($apps, function ($app) {
353
-			return $app !== 'files';//we add this manually
354
-		});
355
-		sort($apps);
356
-		array_unshift($apps, 'files');
357
-		return $apps;
358
-	}
359
-
360
-	/**
361
-	 * checks whether or not an app is enabled
362
-	 *
363
-	 * @param string $app app
364
-	 * @return bool
365
-	 * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
366
-	 *
367
-	 * This function checks whether or not an app is enabled.
368
-	 */
369
-	public static function isEnabled(string $app): bool {
370
-		return \OC::$server->getAppManager()->isEnabledForUser($app);
371
-	}
372
-
373
-	/**
374
-	 * enables an app
375
-	 *
376
-	 * @param string $appId
377
-	 * @param array $groups (optional) when set, only these groups will have access to the app
378
-	 * @throws \Exception
379
-	 * @return void
380
-	 *
381
-	 * This function set an app as enabled in appconfig.
382
-	 */
383
-	public function enable(string $appId,
384
-						   array $groups = []) {
385
-
386
-		// Check if app is already downloaded
387
-		/** @var Installer $installer */
388
-		$installer = \OC::$server->query(Installer::class);
389
-		$isDownloaded = $installer->isDownloaded($appId);
390
-
391
-		if(!$isDownloaded) {
392
-			$installer->downloadApp($appId);
393
-		}
394
-
395
-		$installer->installApp($appId);
396
-
397
-		$appManager = \OC::$server->getAppManager();
398
-		if ($groups !== []) {
399
-			$groupManager = \OC::$server->getGroupManager();
400
-			$groupsList = [];
401
-			foreach ($groups as $group) {
402
-				$groupItem = $groupManager->get($group);
403
-				if ($groupItem instanceof \OCP\IGroup) {
404
-					$groupsList[] = $groupManager->get($group);
405
-				}
406
-			}
407
-			$appManager->enableAppForGroups($appId, $groupsList);
408
-		} else {
409
-			$appManager->enableApp($appId);
410
-		}
411
-	}
412
-
413
-	/**
414
-	 * Get the path where to install apps
415
-	 *
416
-	 * @return string|false
417
-	 */
418
-	public static function getInstallPath() {
419
-		if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
420
-			return false;
421
-		}
422
-
423
-		foreach (OC::$APPSROOTS as $dir) {
424
-			if (isset($dir['writable']) && $dir['writable'] === true) {
425
-				return $dir['path'];
426
-			}
427
-		}
428
-
429
-		\OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
430
-		return null;
431
-	}
432
-
433
-
434
-	/**
435
-	 * search for an app in all app-directories
436
-	 *
437
-	 * @param string $appId
438
-	 * @return false|string
439
-	 */
440
-	public static function findAppInDirectories(string $appId) {
441
-		$sanitizedAppId = self::cleanAppId($appId);
442
-		if($sanitizedAppId !== $appId) {
443
-			return false;
444
-		}
445
-		static $app_dir = [];
446
-
447
-		if (isset($app_dir[$appId])) {
448
-			return $app_dir[$appId];
449
-		}
450
-
451
-		$possibleApps = [];
452
-		foreach (OC::$APPSROOTS as $dir) {
453
-			if (file_exists($dir['path'] . '/' . $appId)) {
454
-				$possibleApps[] = $dir;
455
-			}
456
-		}
457
-
458
-		if (empty($possibleApps)) {
459
-			return false;
460
-		} elseif (count($possibleApps) === 1) {
461
-			$dir = array_shift($possibleApps);
462
-			$app_dir[$appId] = $dir;
463
-			return $dir;
464
-		} else {
465
-			$versionToLoad = [];
466
-			foreach ($possibleApps as $possibleApp) {
467
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
468
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
469
-					$versionToLoad = array(
470
-						'dir' => $possibleApp,
471
-						'version' => $version,
472
-					);
473
-				}
474
-			}
475
-			$app_dir[$appId] = $versionToLoad['dir'];
476
-			return $versionToLoad['dir'];
477
-			//TODO - write test
478
-		}
479
-	}
480
-
481
-	/**
482
-	 * Get the directory for the given app.
483
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
484
-	 *
485
-	 * @param string $appId
486
-	 * @return string|false
487
-	 */
488
-	public static function getAppPath(string $appId) {
489
-		if ($appId === null || trim($appId) === '') {
490
-			return false;
491
-		}
492
-
493
-		if (($dir = self::findAppInDirectories($appId)) != false) {
494
-			return $dir['path'] . '/' . $appId;
495
-		}
496
-		return false;
497
-	}
498
-
499
-	/**
500
-	 * Get the path for the given app on the access
501
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
502
-	 *
503
-	 * @param string $appId
504
-	 * @return string|false
505
-	 */
506
-	public static function getAppWebPath(string $appId) {
507
-		if (($dir = self::findAppInDirectories($appId)) != false) {
508
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
509
-		}
510
-		return false;
511
-	}
512
-
513
-	/**
514
-	 * get the last version of the app from appinfo/info.xml
515
-	 *
516
-	 * @param string $appId
517
-	 * @param bool $useCache
518
-	 * @return string
519
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
520
-	 */
521
-	public static function getAppVersion(string $appId, bool $useCache = true): string {
522
-		return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
523
-	}
524
-
525
-	/**
526
-	 * get app's version based on it's path
527
-	 *
528
-	 * @param string $path
529
-	 * @return string
530
-	 */
531
-	public static function getAppVersionByPath(string $path): string {
532
-		$infoFile = $path . '/appinfo/info.xml';
533
-		$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
534
-		return isset($appData['version']) ? $appData['version'] : '';
535
-	}
536
-
537
-
538
-	/**
539
-	 * Read all app metadata from the info.xml file
540
-	 *
541
-	 * @param string $appId id of the app or the path of the info.xml file
542
-	 * @param bool $path
543
-	 * @param string $lang
544
-	 * @return array|null
545
-	 * @note all data is read from info.xml, not just pre-defined fields
546
-	 * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
547
-	 */
548
-	public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
549
-		return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
550
-	}
551
-
552
-	/**
553
-	 * Returns the navigation
554
-	 *
555
-	 * @return array
556
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
557
-	 *
558
-	 * This function returns an array containing all entries added. The
559
-	 * entries are sorted by the key 'order' ascending. Additional to the keys
560
-	 * given for each app the following keys exist:
561
-	 *   - active: boolean, signals if the user is on this navigation entry
562
-	 */
563
-	public static function getNavigation(): array {
564
-		return OC::$server->getNavigationManager()->getAll();
565
-	}
566
-
567
-	/**
568
-	 * Returns the Settings Navigation
569
-	 *
570
-	 * @return string[]
571
-	 * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
572
-	 *
573
-	 * This function returns an array containing all settings pages added. The
574
-	 * entries are sorted by the key 'order' ascending.
575
-	 */
576
-	public static function getSettingsNavigation(): array {
577
-		return OC::$server->getNavigationManager()->getAll('settings');
578
-	}
579
-
580
-	/**
581
-	 * get the id of loaded app
582
-	 *
583
-	 * @return string
584
-	 */
585
-	public static function getCurrentApp(): string {
586
-		$request = \OC::$server->getRequest();
587
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
588
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
589
-		if (empty($topFolder)) {
590
-			$path_info = $request->getPathInfo();
591
-			if ($path_info) {
592
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
593
-			}
594
-		}
595
-		if ($topFolder == 'apps') {
596
-			$length = strlen($topFolder);
597
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
598
-		} else {
599
-			return $topFolder;
600
-		}
601
-	}
602
-
603
-	/**
604
-	 * @param string $type
605
-	 * @return array
606
-	 */
607
-	public static function getForms(string $type): array {
608
-		$forms = [];
609
-		switch ($type) {
610
-			case 'admin':
611
-				$source = self::$adminForms;
612
-				break;
613
-			case 'personal':
614
-				$source = self::$personalForms;
615
-				break;
616
-			default:
617
-				return [];
618
-		}
619
-		foreach ($source as $form) {
620
-			$forms[] = include $form;
621
-		}
622
-		return $forms;
623
-	}
624
-
625
-	/**
626
-	 * register an admin form to be shown
627
-	 *
628
-	 * @param string $app
629
-	 * @param string $page
630
-	 */
631
-	public static function registerAdmin(string $app, string $page) {
632
-		self::$adminForms[] = $app . '/' . $page . '.php';
633
-	}
634
-
635
-	/**
636
-	 * register a personal form to be shown
637
-	 * @param string $app
638
-	 * @param string $page
639
-	 */
640
-	public static function registerPersonal(string $app, string $page) {
641
-		self::$personalForms[] = $app . '/' . $page . '.php';
642
-	}
643
-
644
-	/**
645
-	 * @param array $entry
646
-	 */
647
-	public static function registerLogIn(array $entry) {
648
-		self::$altLogin[] = $entry;
649
-	}
650
-
651
-	/**
652
-	 * @return array
653
-	 */
654
-	public static function getAlternativeLogIns(): array {
655
-		return self::$altLogin;
656
-	}
657
-
658
-	/**
659
-	 * get a list of all apps in the apps folder
660
-	 *
661
-	 * @return array an array of app names (string IDs)
662
-	 * @todo: change the name of this method to getInstalledApps, which is more accurate
663
-	 */
664
-	public static function getAllApps(): array {
665
-
666
-		$apps = [];
667
-
668
-		foreach (OC::$APPSROOTS as $apps_dir) {
669
-			if (!is_readable($apps_dir['path'])) {
670
-				\OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
671
-				continue;
672
-			}
673
-			$dh = opendir($apps_dir['path']);
674
-
675
-			if (is_resource($dh)) {
676
-				while (($file = readdir($dh)) !== false) {
677
-
678
-					if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
679
-
680
-						$apps[] = $file;
681
-					}
682
-				}
683
-			}
684
-		}
685
-
686
-		$apps = array_unique($apps);
687
-
688
-		return $apps;
689
-	}
690
-
691
-	/**
692
-	 * List all apps, this is used in apps.php
693
-	 *
694
-	 * @return array
695
-	 */
696
-	public function listAllApps(): array {
697
-		$installedApps = OC_App::getAllApps();
698
-
699
-		$appManager = \OC::$server->getAppManager();
700
-		//we don't want to show configuration for these
701
-		$blacklist = $appManager->getAlwaysEnabledApps();
702
-		$appList = [];
703
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
704
-		$urlGenerator = \OC::$server->getURLGenerator();
705
-
706
-		foreach ($installedApps as $app) {
707
-			if (array_search($app, $blacklist) === false) {
708
-
709
-				$info = OC_App::getAppInfo($app, false, $langCode);
710
-				if (!is_array($info)) {
711
-					\OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
712
-					continue;
713
-				}
714
-
715
-				if (!isset($info['name'])) {
716
-					\OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
717
-					continue;
718
-				}
719
-
720
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
721
-				$info['groups'] = null;
722
-				if ($enabled === 'yes') {
723
-					$active = true;
724
-				} else if ($enabled === 'no') {
725
-					$active = false;
726
-				} else {
727
-					$active = true;
728
-					$info['groups'] = $enabled;
729
-				}
730
-
731
-				$info['active'] = $active;
732
-
733
-				if ($appManager->isShipped($app)) {
734
-					$info['internal'] = true;
735
-					$info['level'] = self::officialApp;
736
-					$info['removable'] = false;
737
-				} else {
738
-					$info['internal'] = false;
739
-					$info['removable'] = true;
740
-				}
741
-
742
-				$appPath = self::getAppPath($app);
743
-				if($appPath !== false) {
744
-					$appIcon = $appPath . '/img/' . $app . '.svg';
745
-					if (file_exists($appIcon)) {
746
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
747
-						$info['previewAsIcon'] = true;
748
-					} else {
749
-						$appIcon = $appPath . '/img/app.svg';
750
-						if (file_exists($appIcon)) {
751
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
752
-							$info['previewAsIcon'] = true;
753
-						}
754
-					}
755
-				}
756
-				// fix documentation
757
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
758
-					foreach ($info['documentation'] as $key => $url) {
759
-						// If it is not an absolute URL we assume it is a key
760
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
761
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
762
-							$url = $urlGenerator->linkToDocs($url);
763
-						}
764
-
765
-						$info['documentation'][$key] = $url;
766
-					}
767
-				}
768
-
769
-				$info['version'] = OC_App::getAppVersion($app);
770
-				$appList[] = $info;
771
-			}
772
-		}
773
-
774
-		return $appList;
775
-	}
776
-
777
-	public static function shouldUpgrade(string $app): bool {
778
-		$versions = self::getAppVersions();
779
-		$currentVersion = OC_App::getAppVersion($app);
780
-		if ($currentVersion && isset($versions[$app])) {
781
-			$installedVersion = $versions[$app];
782
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
783
-				return true;
784
-			}
785
-		}
786
-		return false;
787
-	}
788
-
789
-	/**
790
-	 * Adjust the number of version parts of $version1 to match
791
-	 * the number of version parts of $version2.
792
-	 *
793
-	 * @param string $version1 version to adjust
794
-	 * @param string $version2 version to take the number of parts from
795
-	 * @return string shortened $version1
796
-	 */
797
-	private static function adjustVersionParts(string $version1, string $version2): string {
798
-		$version1 = explode('.', $version1);
799
-		$version2 = explode('.', $version2);
800
-		// reduce $version1 to match the number of parts in $version2
801
-		while (count($version1) > count($version2)) {
802
-			array_pop($version1);
803
-		}
804
-		// if $version1 does not have enough parts, add some
805
-		while (count($version1) < count($version2)) {
806
-			$version1[] = '0';
807
-		}
808
-		return implode('.', $version1);
809
-	}
810
-
811
-	/**
812
-	 * Check whether the current ownCloud version matches the given
813
-	 * application's version requirements.
814
-	 *
815
-	 * The comparison is made based on the number of parts that the
816
-	 * app info version has. For example for ownCloud 6.0.3 if the
817
-	 * app info version is expecting version 6.0, the comparison is
818
-	 * made on the first two parts of the ownCloud version.
819
-	 * This means that it's possible to specify "requiremin" => 6
820
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
821
-	 *
822
-	 * @param string $ocVersion ownCloud version to check against
823
-	 * @param array $appInfo app info (from xml)
824
-	 *
825
-	 * @return boolean true if compatible, otherwise false
826
-	 */
827
-	public static function isAppCompatible(string $ocVersion, array $appInfo): bool {
828
-		$requireMin = '';
829
-		$requireMax = '';
830
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
831
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
832
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
833
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
834
-		} else if (isset($appInfo['requiremin'])) {
835
-			$requireMin = $appInfo['requiremin'];
836
-		} else if (isset($appInfo['require'])) {
837
-			$requireMin = $appInfo['require'];
838
-		}
839
-
840
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
841
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
842
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
843
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
844
-		} else if (isset($appInfo['requiremax'])) {
845
-			$requireMax = $appInfo['requiremax'];
846
-		}
847
-
848
-		if (!empty($requireMin)
849
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
850
-		) {
851
-
852
-			return false;
853
-		}
854
-
855
-		if (!empty($requireMax)
856
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
857
-		) {
858
-			return false;
859
-		}
860
-
861
-		return true;
862
-	}
863
-
864
-	/**
865
-	 * get the installed version of all apps
866
-	 */
867
-	public static function getAppVersions() {
868
-		static $versions;
869
-
870
-		if(!$versions) {
871
-			$appConfig = \OC::$server->getAppConfig();
872
-			$versions = $appConfig->getValues(false, 'installed_version');
873
-		}
874
-		return $versions;
875
-	}
876
-
877
-	/**
878
-	 * update the database for the app and call the update script
879
-	 *
880
-	 * @param string $appId
881
-	 * @return bool
882
-	 */
883
-	public static function updateApp(string $appId): bool {
884
-		$appPath = self::getAppPath($appId);
885
-		if($appPath === false) {
886
-			return false;
887
-		}
888
-		self::registerAutoloading($appId, $appPath);
889
-
890
-		\OC::$server->getAppManager()->clearAppsCache();
891
-		$appData = self::getAppInfo($appId);
892
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
893
-
894
-		if (file_exists($appPath . '/appinfo/database.xml')) {
895
-			OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
896
-		} else {
897
-			$ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
898
-			$ms->migrate();
899
-		}
900
-
901
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
902
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
903
-		// update appversion in app manager
904
-		\OC::$server->getAppManager()->clearAppsCache();
905
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
906
-
907
-		// run upgrade code
908
-		if (file_exists($appPath . '/appinfo/update.php')) {
909
-			self::loadApp($appId);
910
-			include $appPath . '/appinfo/update.php';
911
-		}
912
-		self::setupBackgroundJobs($appData['background-jobs']);
913
-
914
-		//set remote/public handlers
915
-		if (array_key_exists('ocsid', $appData)) {
916
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
917
-		} elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
918
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
919
-		}
920
-		foreach ($appData['remote'] as $name => $path) {
921
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
922
-		}
923
-		foreach ($appData['public'] as $name => $path) {
924
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
925
-		}
926
-
927
-		self::setAppTypes($appId);
928
-
929
-		$version = \OC_App::getAppVersion($appId);
930
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
931
-
932
-		\OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
933
-			ManagerEvent::EVENT_APP_UPDATE, $appId
934
-		));
935
-
936
-		return true;
937
-	}
938
-
939
-	/**
940
-	 * @param string $appId
941
-	 * @param string[] $steps
942
-	 * @throws \OC\NeedsUpdateException
943
-	 */
944
-	public static function executeRepairSteps(string $appId, array $steps) {
945
-		if (empty($steps)) {
946
-			return;
947
-		}
948
-		// load the app
949
-		self::loadApp($appId);
950
-
951
-		$dispatcher = OC::$server->getEventDispatcher();
952
-
953
-		// load the steps
954
-		$r = new Repair([], $dispatcher);
955
-		foreach ($steps as $step) {
956
-			try {
957
-				$r->addStep($step);
958
-			} catch (Exception $ex) {
959
-				$r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
960
-				\OC::$server->getLogger()->logException($ex);
961
-			}
962
-		}
963
-		// run the steps
964
-		$r->run();
965
-	}
966
-
967
-	public static function setupBackgroundJobs(array $jobs) {
968
-		$queue = \OC::$server->getJobList();
969
-		foreach ($jobs as $job) {
970
-			$queue->add($job);
971
-		}
972
-	}
973
-
974
-	/**
975
-	 * @param string $appId
976
-	 * @param string[] $steps
977
-	 */
978
-	private static function setupLiveMigrations(string $appId, array $steps) {
979
-		$queue = \OC::$server->getJobList();
980
-		foreach ($steps as $step) {
981
-			$queue->add('OC\Migration\BackgroundRepair', [
982
-				'app' => $appId,
983
-				'step' => $step]);
984
-		}
985
-	}
986
-
987
-	/**
988
-	 * @param string $appId
989
-	 * @return \OC\Files\View|false
990
-	 */
991
-	public static function getStorage(string $appId) {
992
-		if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
993
-			if (\OC::$server->getUserSession()->isLoggedIn()) {
994
-				$view = new \OC\Files\View('/' . OC_User::getUser());
995
-				if (!$view->file_exists($appId)) {
996
-					$view->mkdir($appId);
997
-				}
998
-				return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
999
-			} else {
1000
-				\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1001
-				return false;
1002
-			}
1003
-		} else {
1004
-			\OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1005
-			return false;
1006
-		}
1007
-	}
1008
-
1009
-	protected static function findBestL10NOption(array $options, string $lang): string {
1010
-		// only a single option
1011
-		if (isset($options['@value'])) {
1012
-			return $options['@value'];
1013
-		}
1014
-
1015
-		$fallback = $similarLangFallback = $englishFallback = false;
1016
-
1017
-		$lang = strtolower($lang);
1018
-		$similarLang = $lang;
1019
-		if (strpos($similarLang, '_')) {
1020
-			// For "de_DE" we want to find "de" and the other way around
1021
-			$similarLang = substr($lang, 0, strpos($lang, '_'));
1022
-		}
1023
-
1024
-		foreach ($options as $option) {
1025
-			if (is_array($option)) {
1026
-				if ($fallback === false) {
1027
-					$fallback = $option['@value'];
1028
-				}
1029
-
1030
-				if (!isset($option['@attributes']['lang'])) {
1031
-					continue;
1032
-				}
1033
-
1034
-				$attributeLang = strtolower($option['@attributes']['lang']);
1035
-				if ($attributeLang === $lang) {
1036
-					return $option['@value'];
1037
-				}
1038
-
1039
-				if ($attributeLang === $similarLang) {
1040
-					$similarLangFallback = $option['@value'];
1041
-				} else if (strpos($attributeLang, $similarLang . '_') === 0) {
1042
-					if ($similarLangFallback === false) {
1043
-						$similarLangFallback =  $option['@value'];
1044
-					}
1045
-				}
1046
-			} else {
1047
-				$englishFallback = $option;
1048
-			}
1049
-		}
1050
-
1051
-		if ($similarLangFallback !== false) {
1052
-			return $similarLangFallback;
1053
-		} else if ($englishFallback !== false) {
1054
-			return $englishFallback;
1055
-		}
1056
-		return (string) $fallback;
1057
-	}
1058
-
1059
-	/**
1060
-	 * parses the app data array and enhanced the 'description' value
1061
-	 *
1062
-	 * @param array $data the app data
1063
-	 * @param string $lang
1064
-	 * @return array improved app data
1065
-	 */
1066
-	public static function parseAppInfo(array $data, $lang = null): array {
1067
-
1068
-		if ($lang && isset($data['name']) && is_array($data['name'])) {
1069
-			$data['name'] = self::findBestL10NOption($data['name'], $lang);
1070
-		}
1071
-		if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1072
-			$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1073
-		}
1074
-		if ($lang && isset($data['description']) && is_array($data['description'])) {
1075
-			$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1076
-		} else if (isset($data['description']) && is_string($data['description'])) {
1077
-			$data['description'] = trim($data['description']);
1078
-		} else  {
1079
-			$data['description'] = '';
1080
-		}
1081
-
1082
-		return $data;
1083
-	}
1084
-
1085
-	/**
1086
-	 * @param \OCP\IConfig $config
1087
-	 * @param \OCP\IL10N $l
1088
-	 * @param array $info
1089
-	 * @throws \Exception
1090
-	 */
1091
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info) {
1092
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1093
-		$missing = $dependencyAnalyzer->analyze($info);
1094
-		if (!empty($missing)) {
1095
-			$missingMsg = implode(PHP_EOL, $missing);
1096
-			throw new \Exception(
1097
-				$l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1098
-					[$info['name'], $missingMsg]
1099
-				)
1100
-			);
1101
-		}
1102
-	}
67
+    static private $adminForms = [];
68
+    static private $personalForms = [];
69
+    static private $appTypes = [];
70
+    static private $loadedApps = [];
71
+    static private $altLogin = [];
72
+    static private $alreadyRegistered = [];
73
+    const officialApp = 200;
74
+
75
+    /**
76
+     * clean the appId
77
+     *
78
+     * @param string $app AppId that needs to be cleaned
79
+     * @return string
80
+     */
81
+    public static function cleanAppId(string $app): string {
82
+        return str_replace(array('\0', '/', '\\', '..'), '', $app);
83
+    }
84
+
85
+    /**
86
+     * Check if an app is loaded
87
+     *
88
+     * @param string $app
89
+     * @return bool
90
+     */
91
+    public static function isAppLoaded(string $app): bool {
92
+        return in_array($app, self::$loadedApps, true);
93
+    }
94
+
95
+    /**
96
+     * loads all apps
97
+     *
98
+     * @param string[] $types
99
+     * @return bool
100
+     *
101
+     * This function walks through the ownCloud directory and loads all apps
102
+     * it can find. A directory contains an app if the file /appinfo/info.xml
103
+     * exists.
104
+     *
105
+     * if $types is set to non-empty array, only apps of those types will be loaded
106
+     */
107
+    public static function loadApps(array $types = []): bool {
108
+        if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
109
+            return false;
110
+        }
111
+        // Load the enabled apps here
112
+        $apps = self::getEnabledApps();
113
+
114
+        // Add each apps' folder as allowed class path
115
+        foreach($apps as $app) {
116
+            $path = self::getAppPath($app);
117
+            if($path !== false) {
118
+                self::registerAutoloading($app, $path);
119
+            }
120
+        }
121
+
122
+        // prevent app.php from printing output
123
+        ob_start();
124
+        foreach ($apps as $app) {
125
+            if (($types === [] or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
126
+                self::loadApp($app);
127
+            }
128
+        }
129
+        ob_end_clean();
130
+
131
+        return true;
132
+    }
133
+
134
+    /**
135
+     * load a single app
136
+     *
137
+     * @param string $app
138
+     * @throws Exception
139
+     */
140
+    public static function loadApp(string $app) {
141
+        self::$loadedApps[] = $app;
142
+        $appPath = self::getAppPath($app);
143
+        if($appPath === false) {
144
+            return;
145
+        }
146
+
147
+        // in case someone calls loadApp() directly
148
+        self::registerAutoloading($app, $appPath);
149
+
150
+        if (is_file($appPath . '/appinfo/app.php')) {
151
+            \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
152
+            try {
153
+                self::requireAppFile($app);
154
+            } catch (Error $ex) {
155
+                \OC::$server->getLogger()->logException($ex);
156
+                if (!\OC::$server->getAppManager()->isShipped($app)) {
157
+                    // Only disable apps which are not shipped
158
+                    \OC::$server->getAppManager()->disableApp($app);
159
+                }
160
+            }
161
+            \OC::$server->getEventLogger()->end('load_app_' . $app);
162
+        }
163
+
164
+        $info = self::getAppInfo($app);
165
+        if (!empty($info['activity']['filters'])) {
166
+            foreach ($info['activity']['filters'] as $filter) {
167
+                \OC::$server->getActivityManager()->registerFilter($filter);
168
+            }
169
+        }
170
+        if (!empty($info['activity']['settings'])) {
171
+            foreach ($info['activity']['settings'] as $setting) {
172
+                \OC::$server->getActivityManager()->registerSetting($setting);
173
+            }
174
+        }
175
+        if (!empty($info['activity']['providers'])) {
176
+            foreach ($info['activity']['providers'] as $provider) {
177
+                \OC::$server->getActivityManager()->registerProvider($provider);
178
+            }
179
+        }
180
+
181
+        if (!empty($info['settings']['admin'])) {
182
+            foreach ($info['settings']['admin'] as $setting) {
183
+                \OC::$server->getSettingsManager()->registerSetting('admin', $setting);
184
+            }
185
+        }
186
+        if (!empty($info['settings']['admin-section'])) {
187
+            foreach ($info['settings']['admin-section'] as $section) {
188
+                \OC::$server->getSettingsManager()->registerSection('admin', $section);
189
+            }
190
+        }
191
+        if (!empty($info['settings']['personal'])) {
192
+            foreach ($info['settings']['personal'] as $setting) {
193
+                \OC::$server->getSettingsManager()->registerSetting('personal', $setting);
194
+            }
195
+        }
196
+        if (!empty($info['settings']['personal-section'])) {
197
+            foreach ($info['settings']['personal-section'] as $section) {
198
+                \OC::$server->getSettingsManager()->registerSection('personal', $section);
199
+            }
200
+        }
201
+
202
+        if (!empty($info['collaboration']['plugins'])) {
203
+            // deal with one or many plugin entries
204
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
205
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
206
+            foreach ($plugins as $plugin) {
207
+                if($plugin['@attributes']['type'] === 'collaborator-search') {
208
+                    $pluginInfo = [
209
+                        'shareType' => $plugin['@attributes']['share-type'],
210
+                        'class' => $plugin['@value'],
211
+                    ];
212
+                    \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo);
213
+                } else if ($plugin['@attributes']['type'] === 'autocomplete-sort') {
214
+                    \OC::$server->getAutoCompleteManager()->registerSorter($plugin['@value']);
215
+                }
216
+            }
217
+        }
218
+    }
219
+
220
+    /**
221
+     * @internal
222
+     * @param string $app
223
+     * @param string $path
224
+     */
225
+    public static function registerAutoloading(string $app, string $path) {
226
+        $key = $app . '-' . $path;
227
+        if(isset(self::$alreadyRegistered[$key])) {
228
+            return;
229
+        }
230
+
231
+        self::$alreadyRegistered[$key] = true;
232
+
233
+        // Register on PSR-4 composer autoloader
234
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
235
+        \OC::$server->registerNamespace($app, $appNamespace);
236
+
237
+        if (file_exists($path . '/composer/autoload.php')) {
238
+            require_once $path . '/composer/autoload.php';
239
+        } else {
240
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
241
+            // Register on legacy autoloader
242
+            \OC::$loader->addValidRoot($path);
243
+        }
244
+
245
+        // Register Test namespace only when testing
246
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
247
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
248
+        }
249
+    }
250
+
251
+    /**
252
+     * Load app.php from the given app
253
+     *
254
+     * @param string $app app name
255
+     * @throws Error
256
+     */
257
+    private static function requireAppFile(string $app) {
258
+        // encapsulated here to avoid variable scope conflicts
259
+        require_once $app . '/appinfo/app.php';
260
+    }
261
+
262
+    /**
263
+     * check if an app is of a specific type
264
+     *
265
+     * @param string $app
266
+     * @param array $types
267
+     * @return bool
268
+     */
269
+    public static function isType(string $app, array $types): bool {
270
+        $appTypes = self::getAppTypes($app);
271
+        foreach ($types as $type) {
272
+            if (array_search($type, $appTypes) !== false) {
273
+                return true;
274
+            }
275
+        }
276
+        return false;
277
+    }
278
+
279
+    /**
280
+     * get the types of an app
281
+     *
282
+     * @param string $app
283
+     * @return array
284
+     */
285
+    private static function getAppTypes(string $app): array {
286
+        //load the cache
287
+        if (count(self::$appTypes) == 0) {
288
+            self::$appTypes = \OC::$server->getAppConfig()->getValues(false, 'types');
289
+        }
290
+
291
+        if (isset(self::$appTypes[$app])) {
292
+            return explode(',', self::$appTypes[$app]);
293
+        }
294
+
295
+        return [];
296
+    }
297
+
298
+    /**
299
+     * read app types from info.xml and cache them in the database
300
+     */
301
+    public static function setAppTypes(string $app) {
302
+        $appManager = \OC::$server->getAppManager();
303
+        $appData = $appManager->getAppInfo($app);
304
+        if(!is_array($appData)) {
305
+            return;
306
+        }
307
+
308
+        if (isset($appData['types'])) {
309
+            $appTypes = implode(',', $appData['types']);
310
+        } else {
311
+            $appTypes = '';
312
+            $appData['types'] = [];
313
+        }
314
+
315
+        $config = \OC::$server->getConfig();
316
+        $config->setAppValue($app, 'types', $appTypes);
317
+
318
+        if ($appManager->hasProtectedAppType($appData['types'])) {
319
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
320
+            if ($enabled !== 'yes' && $enabled !== 'no') {
321
+                $config->setAppValue($app, 'enabled', 'yes');
322
+            }
323
+        }
324
+    }
325
+
326
+    /**
327
+     * Returns apps enabled for the current user.
328
+     *
329
+     * @param bool $forceRefresh whether to refresh the cache
330
+     * @param bool $all whether to return apps for all users, not only the
331
+     * currently logged in one
332
+     * @return string[]
333
+     */
334
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
335
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
336
+            return [];
337
+        }
338
+        // in incognito mode or when logged out, $user will be false,
339
+        // which is also the case during an upgrade
340
+        $appManager = \OC::$server->getAppManager();
341
+        if ($all) {
342
+            $user = null;
343
+        } else {
344
+            $user = \OC::$server->getUserSession()->getUser();
345
+        }
346
+
347
+        if (is_null($user)) {
348
+            $apps = $appManager->getInstalledApps();
349
+        } else {
350
+            $apps = $appManager->getEnabledAppsForUser($user);
351
+        }
352
+        $apps = array_filter($apps, function ($app) {
353
+            return $app !== 'files';//we add this manually
354
+        });
355
+        sort($apps);
356
+        array_unshift($apps, 'files');
357
+        return $apps;
358
+    }
359
+
360
+    /**
361
+     * checks whether or not an app is enabled
362
+     *
363
+     * @param string $app app
364
+     * @return bool
365
+     * @deprecated 13.0.0 use \OC::$server->getAppManager()->isEnabledForUser($appId)
366
+     *
367
+     * This function checks whether or not an app is enabled.
368
+     */
369
+    public static function isEnabled(string $app): bool {
370
+        return \OC::$server->getAppManager()->isEnabledForUser($app);
371
+    }
372
+
373
+    /**
374
+     * enables an app
375
+     *
376
+     * @param string $appId
377
+     * @param array $groups (optional) when set, only these groups will have access to the app
378
+     * @throws \Exception
379
+     * @return void
380
+     *
381
+     * This function set an app as enabled in appconfig.
382
+     */
383
+    public function enable(string $appId,
384
+                            array $groups = []) {
385
+
386
+        // Check if app is already downloaded
387
+        /** @var Installer $installer */
388
+        $installer = \OC::$server->query(Installer::class);
389
+        $isDownloaded = $installer->isDownloaded($appId);
390
+
391
+        if(!$isDownloaded) {
392
+            $installer->downloadApp($appId);
393
+        }
394
+
395
+        $installer->installApp($appId);
396
+
397
+        $appManager = \OC::$server->getAppManager();
398
+        if ($groups !== []) {
399
+            $groupManager = \OC::$server->getGroupManager();
400
+            $groupsList = [];
401
+            foreach ($groups as $group) {
402
+                $groupItem = $groupManager->get($group);
403
+                if ($groupItem instanceof \OCP\IGroup) {
404
+                    $groupsList[] = $groupManager->get($group);
405
+                }
406
+            }
407
+            $appManager->enableAppForGroups($appId, $groupsList);
408
+        } else {
409
+            $appManager->enableApp($appId);
410
+        }
411
+    }
412
+
413
+    /**
414
+     * Get the path where to install apps
415
+     *
416
+     * @return string|false
417
+     */
418
+    public static function getInstallPath() {
419
+        if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) {
420
+            return false;
421
+        }
422
+
423
+        foreach (OC::$APPSROOTS as $dir) {
424
+            if (isset($dir['writable']) && $dir['writable'] === true) {
425
+                return $dir['path'];
426
+            }
427
+        }
428
+
429
+        \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
430
+        return null;
431
+    }
432
+
433
+
434
+    /**
435
+     * search for an app in all app-directories
436
+     *
437
+     * @param string $appId
438
+     * @return false|string
439
+     */
440
+    public static function findAppInDirectories(string $appId) {
441
+        $sanitizedAppId = self::cleanAppId($appId);
442
+        if($sanitizedAppId !== $appId) {
443
+            return false;
444
+        }
445
+        static $app_dir = [];
446
+
447
+        if (isset($app_dir[$appId])) {
448
+            return $app_dir[$appId];
449
+        }
450
+
451
+        $possibleApps = [];
452
+        foreach (OC::$APPSROOTS as $dir) {
453
+            if (file_exists($dir['path'] . '/' . $appId)) {
454
+                $possibleApps[] = $dir;
455
+            }
456
+        }
457
+
458
+        if (empty($possibleApps)) {
459
+            return false;
460
+        } elseif (count($possibleApps) === 1) {
461
+            $dir = array_shift($possibleApps);
462
+            $app_dir[$appId] = $dir;
463
+            return $dir;
464
+        } else {
465
+            $versionToLoad = [];
466
+            foreach ($possibleApps as $possibleApp) {
467
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
468
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
469
+                    $versionToLoad = array(
470
+                        'dir' => $possibleApp,
471
+                        'version' => $version,
472
+                    );
473
+                }
474
+            }
475
+            $app_dir[$appId] = $versionToLoad['dir'];
476
+            return $versionToLoad['dir'];
477
+            //TODO - write test
478
+        }
479
+    }
480
+
481
+    /**
482
+     * Get the directory for the given app.
483
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
484
+     *
485
+     * @param string $appId
486
+     * @return string|false
487
+     */
488
+    public static function getAppPath(string $appId) {
489
+        if ($appId === null || trim($appId) === '') {
490
+            return false;
491
+        }
492
+
493
+        if (($dir = self::findAppInDirectories($appId)) != false) {
494
+            return $dir['path'] . '/' . $appId;
495
+        }
496
+        return false;
497
+    }
498
+
499
+    /**
500
+     * Get the path for the given app on the access
501
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
502
+     *
503
+     * @param string $appId
504
+     * @return string|false
505
+     */
506
+    public static function getAppWebPath(string $appId) {
507
+        if (($dir = self::findAppInDirectories($appId)) != false) {
508
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
509
+        }
510
+        return false;
511
+    }
512
+
513
+    /**
514
+     * get the last version of the app from appinfo/info.xml
515
+     *
516
+     * @param string $appId
517
+     * @param bool $useCache
518
+     * @return string
519
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppVersion()
520
+     */
521
+    public static function getAppVersion(string $appId, bool $useCache = true): string {
522
+        return \OC::$server->getAppManager()->getAppVersion($appId, $useCache);
523
+    }
524
+
525
+    /**
526
+     * get app's version based on it's path
527
+     *
528
+     * @param string $path
529
+     * @return string
530
+     */
531
+    public static function getAppVersionByPath(string $path): string {
532
+        $infoFile = $path . '/appinfo/info.xml';
533
+        $appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
534
+        return isset($appData['version']) ? $appData['version'] : '';
535
+    }
536
+
537
+
538
+    /**
539
+     * Read all app metadata from the info.xml file
540
+     *
541
+     * @param string $appId id of the app or the path of the info.xml file
542
+     * @param bool $path
543
+     * @param string $lang
544
+     * @return array|null
545
+     * @note all data is read from info.xml, not just pre-defined fields
546
+     * @deprecated 14.0.0 use \OC::$server->getAppManager()->getAppInfo()
547
+     */
548
+    public static function getAppInfo(string $appId, bool $path = false, string $lang = null) {
549
+        return \OC::$server->getAppManager()->getAppInfo($appId, $path, $lang);
550
+    }
551
+
552
+    /**
553
+     * Returns the navigation
554
+     *
555
+     * @return array
556
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll()
557
+     *
558
+     * This function returns an array containing all entries added. The
559
+     * entries are sorted by the key 'order' ascending. Additional to the keys
560
+     * given for each app the following keys exist:
561
+     *   - active: boolean, signals if the user is on this navigation entry
562
+     */
563
+    public static function getNavigation(): array {
564
+        return OC::$server->getNavigationManager()->getAll();
565
+    }
566
+
567
+    /**
568
+     * Returns the Settings Navigation
569
+     *
570
+     * @return string[]
571
+     * @deprecated 14.0.0 use \OC::$server->getNavigationManager()->getAll('settings')
572
+     *
573
+     * This function returns an array containing all settings pages added. The
574
+     * entries are sorted by the key 'order' ascending.
575
+     */
576
+    public static function getSettingsNavigation(): array {
577
+        return OC::$server->getNavigationManager()->getAll('settings');
578
+    }
579
+
580
+    /**
581
+     * get the id of loaded app
582
+     *
583
+     * @return string
584
+     */
585
+    public static function getCurrentApp(): string {
586
+        $request = \OC::$server->getRequest();
587
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
588
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
589
+        if (empty($topFolder)) {
590
+            $path_info = $request->getPathInfo();
591
+            if ($path_info) {
592
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
593
+            }
594
+        }
595
+        if ($topFolder == 'apps') {
596
+            $length = strlen($topFolder);
597
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
598
+        } else {
599
+            return $topFolder;
600
+        }
601
+    }
602
+
603
+    /**
604
+     * @param string $type
605
+     * @return array
606
+     */
607
+    public static function getForms(string $type): array {
608
+        $forms = [];
609
+        switch ($type) {
610
+            case 'admin':
611
+                $source = self::$adminForms;
612
+                break;
613
+            case 'personal':
614
+                $source = self::$personalForms;
615
+                break;
616
+            default:
617
+                return [];
618
+        }
619
+        foreach ($source as $form) {
620
+            $forms[] = include $form;
621
+        }
622
+        return $forms;
623
+    }
624
+
625
+    /**
626
+     * register an admin form to be shown
627
+     *
628
+     * @param string $app
629
+     * @param string $page
630
+     */
631
+    public static function registerAdmin(string $app, string $page) {
632
+        self::$adminForms[] = $app . '/' . $page . '.php';
633
+    }
634
+
635
+    /**
636
+     * register a personal form to be shown
637
+     * @param string $app
638
+     * @param string $page
639
+     */
640
+    public static function registerPersonal(string $app, string $page) {
641
+        self::$personalForms[] = $app . '/' . $page . '.php';
642
+    }
643
+
644
+    /**
645
+     * @param array $entry
646
+     */
647
+    public static function registerLogIn(array $entry) {
648
+        self::$altLogin[] = $entry;
649
+    }
650
+
651
+    /**
652
+     * @return array
653
+     */
654
+    public static function getAlternativeLogIns(): array {
655
+        return self::$altLogin;
656
+    }
657
+
658
+    /**
659
+     * get a list of all apps in the apps folder
660
+     *
661
+     * @return array an array of app names (string IDs)
662
+     * @todo: change the name of this method to getInstalledApps, which is more accurate
663
+     */
664
+    public static function getAllApps(): array {
665
+
666
+        $apps = [];
667
+
668
+        foreach (OC::$APPSROOTS as $apps_dir) {
669
+            if (!is_readable($apps_dir['path'])) {
670
+                \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
671
+                continue;
672
+            }
673
+            $dh = opendir($apps_dir['path']);
674
+
675
+            if (is_resource($dh)) {
676
+                while (($file = readdir($dh)) !== false) {
677
+
678
+                    if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
679
+
680
+                        $apps[] = $file;
681
+                    }
682
+                }
683
+            }
684
+        }
685
+
686
+        $apps = array_unique($apps);
687
+
688
+        return $apps;
689
+    }
690
+
691
+    /**
692
+     * List all apps, this is used in apps.php
693
+     *
694
+     * @return array
695
+     */
696
+    public function listAllApps(): array {
697
+        $installedApps = OC_App::getAllApps();
698
+
699
+        $appManager = \OC::$server->getAppManager();
700
+        //we don't want to show configuration for these
701
+        $blacklist = $appManager->getAlwaysEnabledApps();
702
+        $appList = [];
703
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
704
+        $urlGenerator = \OC::$server->getURLGenerator();
705
+
706
+        foreach ($installedApps as $app) {
707
+            if (array_search($app, $blacklist) === false) {
708
+
709
+                $info = OC_App::getAppInfo($app, false, $langCode);
710
+                if (!is_array($info)) {
711
+                    \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
712
+                    continue;
713
+                }
714
+
715
+                if (!isset($info['name'])) {
716
+                    \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
717
+                    continue;
718
+                }
719
+
720
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
721
+                $info['groups'] = null;
722
+                if ($enabled === 'yes') {
723
+                    $active = true;
724
+                } else if ($enabled === 'no') {
725
+                    $active = false;
726
+                } else {
727
+                    $active = true;
728
+                    $info['groups'] = $enabled;
729
+                }
730
+
731
+                $info['active'] = $active;
732
+
733
+                if ($appManager->isShipped($app)) {
734
+                    $info['internal'] = true;
735
+                    $info['level'] = self::officialApp;
736
+                    $info['removable'] = false;
737
+                } else {
738
+                    $info['internal'] = false;
739
+                    $info['removable'] = true;
740
+                }
741
+
742
+                $appPath = self::getAppPath($app);
743
+                if($appPath !== false) {
744
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
745
+                    if (file_exists($appIcon)) {
746
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
747
+                        $info['previewAsIcon'] = true;
748
+                    } else {
749
+                        $appIcon = $appPath . '/img/app.svg';
750
+                        if (file_exists($appIcon)) {
751
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
752
+                            $info['previewAsIcon'] = true;
753
+                        }
754
+                    }
755
+                }
756
+                // fix documentation
757
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
758
+                    foreach ($info['documentation'] as $key => $url) {
759
+                        // If it is not an absolute URL we assume it is a key
760
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
761
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
762
+                            $url = $urlGenerator->linkToDocs($url);
763
+                        }
764
+
765
+                        $info['documentation'][$key] = $url;
766
+                    }
767
+                }
768
+
769
+                $info['version'] = OC_App::getAppVersion($app);
770
+                $appList[] = $info;
771
+            }
772
+        }
773
+
774
+        return $appList;
775
+    }
776
+
777
+    public static function shouldUpgrade(string $app): bool {
778
+        $versions = self::getAppVersions();
779
+        $currentVersion = OC_App::getAppVersion($app);
780
+        if ($currentVersion && isset($versions[$app])) {
781
+            $installedVersion = $versions[$app];
782
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
783
+                return true;
784
+            }
785
+        }
786
+        return false;
787
+    }
788
+
789
+    /**
790
+     * Adjust the number of version parts of $version1 to match
791
+     * the number of version parts of $version2.
792
+     *
793
+     * @param string $version1 version to adjust
794
+     * @param string $version2 version to take the number of parts from
795
+     * @return string shortened $version1
796
+     */
797
+    private static function adjustVersionParts(string $version1, string $version2): string {
798
+        $version1 = explode('.', $version1);
799
+        $version2 = explode('.', $version2);
800
+        // reduce $version1 to match the number of parts in $version2
801
+        while (count($version1) > count($version2)) {
802
+            array_pop($version1);
803
+        }
804
+        // if $version1 does not have enough parts, add some
805
+        while (count($version1) < count($version2)) {
806
+            $version1[] = '0';
807
+        }
808
+        return implode('.', $version1);
809
+    }
810
+
811
+    /**
812
+     * Check whether the current ownCloud version matches the given
813
+     * application's version requirements.
814
+     *
815
+     * The comparison is made based on the number of parts that the
816
+     * app info version has. For example for ownCloud 6.0.3 if the
817
+     * app info version is expecting version 6.0, the comparison is
818
+     * made on the first two parts of the ownCloud version.
819
+     * This means that it's possible to specify "requiremin" => 6
820
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
821
+     *
822
+     * @param string $ocVersion ownCloud version to check against
823
+     * @param array $appInfo app info (from xml)
824
+     *
825
+     * @return boolean true if compatible, otherwise false
826
+     */
827
+    public static function isAppCompatible(string $ocVersion, array $appInfo): bool {
828
+        $requireMin = '';
829
+        $requireMax = '';
830
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
831
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
832
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
833
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
834
+        } else if (isset($appInfo['requiremin'])) {
835
+            $requireMin = $appInfo['requiremin'];
836
+        } else if (isset($appInfo['require'])) {
837
+            $requireMin = $appInfo['require'];
838
+        }
839
+
840
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
841
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
842
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
843
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
844
+        } else if (isset($appInfo['requiremax'])) {
845
+            $requireMax = $appInfo['requiremax'];
846
+        }
847
+
848
+        if (!empty($requireMin)
849
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
850
+        ) {
851
+
852
+            return false;
853
+        }
854
+
855
+        if (!empty($requireMax)
856
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
857
+        ) {
858
+            return false;
859
+        }
860
+
861
+        return true;
862
+    }
863
+
864
+    /**
865
+     * get the installed version of all apps
866
+     */
867
+    public static function getAppVersions() {
868
+        static $versions;
869
+
870
+        if(!$versions) {
871
+            $appConfig = \OC::$server->getAppConfig();
872
+            $versions = $appConfig->getValues(false, 'installed_version');
873
+        }
874
+        return $versions;
875
+    }
876
+
877
+    /**
878
+     * update the database for the app and call the update script
879
+     *
880
+     * @param string $appId
881
+     * @return bool
882
+     */
883
+    public static function updateApp(string $appId): bool {
884
+        $appPath = self::getAppPath($appId);
885
+        if($appPath === false) {
886
+            return false;
887
+        }
888
+        self::registerAutoloading($appId, $appPath);
889
+
890
+        \OC::$server->getAppManager()->clearAppsCache();
891
+        $appData = self::getAppInfo($appId);
892
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
893
+
894
+        if (file_exists($appPath . '/appinfo/database.xml')) {
895
+            OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
896
+        } else {
897
+            $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
898
+            $ms->migrate();
899
+        }
900
+
901
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
902
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
903
+        // update appversion in app manager
904
+        \OC::$server->getAppManager()->clearAppsCache();
905
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
906
+
907
+        // run upgrade code
908
+        if (file_exists($appPath . '/appinfo/update.php')) {
909
+            self::loadApp($appId);
910
+            include $appPath . '/appinfo/update.php';
911
+        }
912
+        self::setupBackgroundJobs($appData['background-jobs']);
913
+
914
+        //set remote/public handlers
915
+        if (array_key_exists('ocsid', $appData)) {
916
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
917
+        } elseif(\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
918
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
919
+        }
920
+        foreach ($appData['remote'] as $name => $path) {
921
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
922
+        }
923
+        foreach ($appData['public'] as $name => $path) {
924
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
925
+        }
926
+
927
+        self::setAppTypes($appId);
928
+
929
+        $version = \OC_App::getAppVersion($appId);
930
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
931
+
932
+        \OC::$server->getEventDispatcher()->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
933
+            ManagerEvent::EVENT_APP_UPDATE, $appId
934
+        ));
935
+
936
+        return true;
937
+    }
938
+
939
+    /**
940
+     * @param string $appId
941
+     * @param string[] $steps
942
+     * @throws \OC\NeedsUpdateException
943
+     */
944
+    public static function executeRepairSteps(string $appId, array $steps) {
945
+        if (empty($steps)) {
946
+            return;
947
+        }
948
+        // load the app
949
+        self::loadApp($appId);
950
+
951
+        $dispatcher = OC::$server->getEventDispatcher();
952
+
953
+        // load the steps
954
+        $r = new Repair([], $dispatcher);
955
+        foreach ($steps as $step) {
956
+            try {
957
+                $r->addStep($step);
958
+            } catch (Exception $ex) {
959
+                $r->emit('\OC\Repair', 'error', [$ex->getMessage()]);
960
+                \OC::$server->getLogger()->logException($ex);
961
+            }
962
+        }
963
+        // run the steps
964
+        $r->run();
965
+    }
966
+
967
+    public static function setupBackgroundJobs(array $jobs) {
968
+        $queue = \OC::$server->getJobList();
969
+        foreach ($jobs as $job) {
970
+            $queue->add($job);
971
+        }
972
+    }
973
+
974
+    /**
975
+     * @param string $appId
976
+     * @param string[] $steps
977
+     */
978
+    private static function setupLiveMigrations(string $appId, array $steps) {
979
+        $queue = \OC::$server->getJobList();
980
+        foreach ($steps as $step) {
981
+            $queue->add('OC\Migration\BackgroundRepair', [
982
+                'app' => $appId,
983
+                'step' => $step]);
984
+        }
985
+    }
986
+
987
+    /**
988
+     * @param string $appId
989
+     * @return \OC\Files\View|false
990
+     */
991
+    public static function getStorage(string $appId) {
992
+        if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
993
+            if (\OC::$server->getUserSession()->isLoggedIn()) {
994
+                $view = new \OC\Files\View('/' . OC_User::getUser());
995
+                if (!$view->file_exists($appId)) {
996
+                    $view->mkdir($appId);
997
+                }
998
+                return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
999
+            } else {
1000
+                \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
1001
+                return false;
1002
+            }
1003
+        } else {
1004
+            \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
1005
+            return false;
1006
+        }
1007
+    }
1008
+
1009
+    protected static function findBestL10NOption(array $options, string $lang): string {
1010
+        // only a single option
1011
+        if (isset($options['@value'])) {
1012
+            return $options['@value'];
1013
+        }
1014
+
1015
+        $fallback = $similarLangFallback = $englishFallback = false;
1016
+
1017
+        $lang = strtolower($lang);
1018
+        $similarLang = $lang;
1019
+        if (strpos($similarLang, '_')) {
1020
+            // For "de_DE" we want to find "de" and the other way around
1021
+            $similarLang = substr($lang, 0, strpos($lang, '_'));
1022
+        }
1023
+
1024
+        foreach ($options as $option) {
1025
+            if (is_array($option)) {
1026
+                if ($fallback === false) {
1027
+                    $fallback = $option['@value'];
1028
+                }
1029
+
1030
+                if (!isset($option['@attributes']['lang'])) {
1031
+                    continue;
1032
+                }
1033
+
1034
+                $attributeLang = strtolower($option['@attributes']['lang']);
1035
+                if ($attributeLang === $lang) {
1036
+                    return $option['@value'];
1037
+                }
1038
+
1039
+                if ($attributeLang === $similarLang) {
1040
+                    $similarLangFallback = $option['@value'];
1041
+                } else if (strpos($attributeLang, $similarLang . '_') === 0) {
1042
+                    if ($similarLangFallback === false) {
1043
+                        $similarLangFallback =  $option['@value'];
1044
+                    }
1045
+                }
1046
+            } else {
1047
+                $englishFallback = $option;
1048
+            }
1049
+        }
1050
+
1051
+        if ($similarLangFallback !== false) {
1052
+            return $similarLangFallback;
1053
+        } else if ($englishFallback !== false) {
1054
+            return $englishFallback;
1055
+        }
1056
+        return (string) $fallback;
1057
+    }
1058
+
1059
+    /**
1060
+     * parses the app data array and enhanced the 'description' value
1061
+     *
1062
+     * @param array $data the app data
1063
+     * @param string $lang
1064
+     * @return array improved app data
1065
+     */
1066
+    public static function parseAppInfo(array $data, $lang = null): array {
1067
+
1068
+        if ($lang && isset($data['name']) && is_array($data['name'])) {
1069
+            $data['name'] = self::findBestL10NOption($data['name'], $lang);
1070
+        }
1071
+        if ($lang && isset($data['summary']) && is_array($data['summary'])) {
1072
+            $data['summary'] = self::findBestL10NOption($data['summary'], $lang);
1073
+        }
1074
+        if ($lang && isset($data['description']) && is_array($data['description'])) {
1075
+            $data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
1076
+        } else if (isset($data['description']) && is_string($data['description'])) {
1077
+            $data['description'] = trim($data['description']);
1078
+        } else  {
1079
+            $data['description'] = '';
1080
+        }
1081
+
1082
+        return $data;
1083
+    }
1084
+
1085
+    /**
1086
+     * @param \OCP\IConfig $config
1087
+     * @param \OCP\IL10N $l
1088
+     * @param array $info
1089
+     * @throws \Exception
1090
+     */
1091
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info) {
1092
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
1093
+        $missing = $dependencyAnalyzer->analyze($info);
1094
+        if (!empty($missing)) {
1095
+            $missingMsg = implode(PHP_EOL, $missing);
1096
+            throw new \Exception(
1097
+                $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
1098
+                    [$info['name'], $missingMsg]
1099
+                )
1100
+            );
1101
+        }
1102
+    }
1103 1103
 }
Please login to merge, or discard this patch.
settings/Controller/AppSettingsController.php 3 patches
Doc Comments   +2 added lines, -3 removed lines patch added patch discarded remove patch
@@ -238,7 +238,6 @@  discard block
 block discarded – undo
238 238
 	/**
239 239
 	 * Get all available apps in a category
240 240
 	 *
241
-	 * @param string $category
242 241
 	 * @return JSONResponse
243 242
 	 * @throws \Exception
244 243
 	 */
@@ -415,7 +414,7 @@  discard block
 block discarded – undo
415 414
 	 * apps will be enabled for specific groups only if $groups is defined
416 415
 	 *
417 416
 	 * @PasswordConfirmationRequired
418
-	 * @param array $appIds
417
+	 * @param string[] $appIds
419 418
 	 * @param array $groups
420 419
 	 * @return JSONResponse
421 420
 	 */
@@ -479,7 +478,7 @@  discard block
 block discarded – undo
479 478
 	/**
480 479
 	 * @PasswordConfirmationRequired
481 480
 	 *
482
-	 * @param array $appIds
481
+	 * @param string[] $appIds
483 482
 	 * @return JSONResponse
484 483
 	 */
485 484
 	public function disableApps(array $appIds): JSONResponse {
Please login to merge, or discard this patch.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -152,9 +152,9 @@  discard block
 block discarded – undo
152 152
 	private function getAppsWithUpdates() {
153 153
 		$appClass = new \OC_App();
154 154
 		$apps = $appClass->listAllApps();
155
-		foreach($apps as $key => $app) {
155
+		foreach ($apps as $key => $app) {
156 156
 			$newVersion = $this->installer->isUpdateAvailable($app['id']);
157
-			if($newVersion === false) {
157
+			if ($newVersion === false) {
158 158
 				unset($apps[$key]);
159 159
 			}
160 160
 		}
@@ -189,7 +189,7 @@  discard block
 block discarded – undo
189 189
 
190 190
 		$formattedCategories = [];
191 191
 		$categories = $this->categoryFetcher->get();
192
-		foreach($categories as $category) {
192
+		foreach ($categories as $category) {
193 193
 			$formattedCategories[] = [
194 194
 				'id' => $category['id'],
195 195
 				'ident' => $category['id'],
@@ -220,10 +220,10 @@  discard block
 block discarded – undo
220 220
 
221 221
 		// add bundle information
222 222
 		$bundles = $this->bundleFetcher->getBundles();
223
-		foreach($bundles as $bundle) {
224
-			foreach($bundle->getAppIdentifiers() as $identifier) {
225
-				foreach($this->allApps as &$app) {
226
-					if($app['id'] === $identifier) {
223
+		foreach ($bundles as $bundle) {
224
+			foreach ($bundle->getAppIdentifiers() as $identifier) {
225
+				foreach ($this->allApps as &$app) {
226
+					if ($app['id'] === $identifier) {
227 227
 						$app['bundleId'] = $bundle->getIdentifier();
228 228
 						continue;
229 229
 					}
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
 			$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($appstoreData['screenshots'][0]['url']) : '';
256 256
 
257 257
 			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
258
-			if($newVersion && $this->appManager->isInstalled($appData['id'])) {
258
+			if ($newVersion && $this->appManager->isInstalled($appData['id'])) {
259 259
 				$appData['update'] = $newVersion;
260 260
 			}
261 261
 
@@ -299,44 +299,44 @@  discard block
 block discarded – undo
299 299
 		$versionParser = new VersionParser();
300 300
 		$formattedApps = [];
301 301
 		$apps = $this->appFetcher->get();
302
-		foreach($apps as $app) {
302
+		foreach ($apps as $app) {
303 303
 			// Skip all apps not in the requested category
304 304
 			if ($requestedCategory !== '') {
305 305
 				$isInCategory = false;
306
-				foreach($app['categories'] as $category) {
307
-					if($category === $requestedCategory) {
306
+				foreach ($app['categories'] as $category) {
307
+					if ($category === $requestedCategory) {
308 308
 						$isInCategory = true;
309 309
 					}
310 310
 				}
311
-				if(!$isInCategory) {
311
+				if (!$isInCategory) {
312 312
 					continue;
313 313
 				}
314 314
 			}
315 315
 
316 316
 			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
317 317
 			$nextCloudVersionDependencies = [];
318
-			if($nextCloudVersion->getMinimumVersion() !== '') {
318
+			if ($nextCloudVersion->getMinimumVersion() !== '') {
319 319
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
320 320
 			}
321
-			if($nextCloudVersion->getMaximumVersion() !== '') {
321
+			if ($nextCloudVersion->getMaximumVersion() !== '') {
322 322
 				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
323 323
 			}
324 324
 			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
325 325
 			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
326 326
 			$phpDependencies = [];
327
-			if($phpVersion->getMinimumVersion() !== '') {
327
+			if ($phpVersion->getMinimumVersion() !== '') {
328 328
 				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
329 329
 			}
330
-			if($phpVersion->getMaximumVersion() !== '') {
330
+			if ($phpVersion->getMaximumVersion() !== '') {
331 331
 				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
332 332
 			}
333
-			if(isset($app['releases'][0]['minIntSize'])) {
333
+			if (isset($app['releases'][0]['minIntSize'])) {
334 334
 				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
335 335
 			}
336 336
 			$authors = '';
337
-			foreach($app['authors'] as $key => $author) {
337
+			foreach ($app['authors'] as $key => $author) {
338 338
 				$authors .= $author['name'];
339
-				if($key !== count($app['authors']) - 1) {
339
+				if ($key !== count($app['authors']) - 1) {
340 340
 					$authors .= ', ';
341 341
 				}
342 342
 			}
@@ -344,12 +344,12 @@  discard block
 block discarded – undo
344 344
 			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
345 345
 			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
346 346
 			$groups = null;
347
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
347
+			if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
348 348
 				$groups = $enabledValue;
349 349
 			}
350 350
 
351 351
 			$currentVersion = '';
352
-			if($this->appManager->isInstalled($app['id'])) {
352
+			if ($this->appManager->isInstalled($app['id'])) {
353 353
 				$currentVersion = $this->appManager->getAppVersion($app['id']);
354 354
 			} else {
355 355
 				$currentLanguage = $app['releases'][0]['version'];
@@ -431,7 +431,7 @@  discard block
 block discarded – undo
431 431
 				$installer = \OC::$server->query(Installer::class);
432 432
 				$isDownloaded = $installer->isDownloaded($appId);
433 433
 
434
-				if(!$isDownloaded) {
434
+				if (!$isDownloaded) {
435 435
 					$installer->downloadApp($appId);
436 436
 				}
437 437
 
@@ -504,7 +504,7 @@  discard block
 block discarded – undo
504 504
 	public function uninstallApp(string $appId): JSONResponse {
505 505
 		$appId = OC_App::cleanAppId($appId);
506 506
 		$result = $this->installer->removeApp($appId);
507
-		if($result !== false) {
507
+		if ($result !== false) {
508 508
 			$this->appManager->clearAppsCache();
509 509
 			return new JSONResponse(['data' => ['appid' => $appId]]);
510 510
 		}
@@ -534,8 +534,8 @@  discard block
 block discarded – undo
534 534
 	}
535 535
 
536 536
 	private function sortApps($a, $b) {
537
-		$a = (string)$a['name'];
538
-		$b = (string)$b['name'];
537
+		$a = (string) $a['name'];
538
+		$b = (string) $b['name'];
539 539
 		if ($a === $b) {
540 540
 			return 0;
541 541
 		}
Please login to merge, or discard this patch.
Indentation   +483 added lines, -483 removed lines patch added patch discarded remove patch
@@ -58,488 +58,488 @@
 block discarded – undo
58 58
  */
59 59
 class AppSettingsController extends Controller {
60 60
 
61
-	/** @var \OCP\IL10N */
62
-	private $l10n;
63
-	/** @var IConfig */
64
-	private $config;
65
-	/** @var INavigationManager */
66
-	private $navigationManager;
67
-	/** @var IAppManager */
68
-	private $appManager;
69
-	/** @var CategoryFetcher */
70
-	private $categoryFetcher;
71
-	/** @var AppFetcher */
72
-	private $appFetcher;
73
-	/** @var IFactory */
74
-	private $l10nFactory;
75
-	/** @var BundleFetcher */
76
-	private $bundleFetcher;
77
-	/** @var Installer */
78
-	private $installer;
79
-	/** @var IURLGenerator */
80
-	private $urlGenerator;
81
-	/** @var ILogger */
82
-	private $logger;
83
-
84
-	/** @var array */
85
-	private $allApps = [];
86
-
87
-	/**
88
-	 * @param string $appName
89
-	 * @param IRequest $request
90
-	 * @param IL10N $l10n
91
-	 * @param IConfig $config
92
-	 * @param INavigationManager $navigationManager
93
-	 * @param IAppManager $appManager
94
-	 * @param CategoryFetcher $categoryFetcher
95
-	 * @param AppFetcher $appFetcher
96
-	 * @param IFactory $l10nFactory
97
-	 * @param BundleFetcher $bundleFetcher
98
-	 * @param Installer $installer
99
-	 * @param IURLGenerator $urlGenerator
100
-	 * @param ILogger $logger
101
-	 */
102
-	public function __construct(string $appName,
103
-								IRequest $request,
104
-								IL10N $l10n,
105
-								IConfig $config,
106
-								INavigationManager $navigationManager,
107
-								IAppManager $appManager,
108
-								CategoryFetcher $categoryFetcher,
109
-								AppFetcher $appFetcher,
110
-								IFactory $l10nFactory,
111
-								BundleFetcher $bundleFetcher,
112
-								Installer $installer,
113
-								IURLGenerator $urlGenerator,
114
-								ILogger $logger) {
115
-		parent::__construct($appName, $request);
116
-		$this->l10n = $l10n;
117
-		$this->config = $config;
118
-		$this->navigationManager = $navigationManager;
119
-		$this->appManager = $appManager;
120
-		$this->categoryFetcher = $categoryFetcher;
121
-		$this->appFetcher = $appFetcher;
122
-		$this->l10nFactory = $l10nFactory;
123
-		$this->bundleFetcher = $bundleFetcher;
124
-		$this->installer = $installer;
125
-		$this->urlGenerator = $urlGenerator;
126
-		$this->logger = $logger;
127
-	}
128
-
129
-	/**
130
-	 * @NoCSRFRequired
131
-	 *
132
-	 * @return TemplateResponse
133
-	 */
134
-	public function viewApps(): TemplateResponse {
135
-		\OC_Util::addScript('settings', 'apps');
136
-		\OC_Util::addVendorScript('core', 'marked/marked.min');
137
-		$params = [];
138
-		$params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
139
-		$params['updateCount'] = count($this->getAppsWithUpdates());
140
-		$params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
141
-		$params['bundles'] = $this->getBundles();
142
-		$this->navigationManager->setActiveEntry('core_apps');
143
-
144
-		$templateResponse = new TemplateResponse('settings', 'settings', ['serverData' => $params]);
145
-		$policy = new ContentSecurityPolicy();
146
-		$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
147
-		$templateResponse->setContentSecurityPolicy($policy);
148
-
149
-		return $templateResponse;
150
-	}
151
-
152
-	private function getAppsWithUpdates() {
153
-		$appClass = new \OC_App();
154
-		$apps = $appClass->listAllApps();
155
-		foreach($apps as $key => $app) {
156
-			$newVersion = $this->installer->isUpdateAvailable($app['id']);
157
-			if($newVersion === false) {
158
-				unset($apps[$key]);
159
-			}
160
-		}
161
-		return $apps;
162
-	}
163
-
164
-	private function getBundles() {
165
-		$result = [];
166
-		$bundles = $this->bundleFetcher->getBundles();
167
-		foreach ($bundles as $bundle) {
168
-			$result[] = [
169
-				'name' => $bundle->getName(),
170
-				'id' => $bundle->getIdentifier(),
171
-				'appIdentifiers' => $bundle->getAppIdentifiers()
172
-			];
173
-		}
174
-		return $result;
175
-
176
-	}
177
-
178
-	/**
179
-	 * Get all available categories
180
-	 *
181
-	 * @return JSONResponse
182
-	 */
183
-	public function listCategories(): JSONResponse {
184
-		return new JSONResponse($this->getAllCategories());
185
-	}
186
-
187
-	private function getAllCategories() {
188
-		$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
189
-
190
-		$formattedCategories = [];
191
-		$categories = $this->categoryFetcher->get();
192
-		foreach($categories as $category) {
193
-			$formattedCategories[] = [
194
-				'id' => $category['id'],
195
-				'ident' => $category['id'],
196
-				'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
197
-			];
198
-		}
199
-
200
-		return $formattedCategories;
201
-	}
202
-
203
-	private function fetchApps() {
204
-		$appClass = new \OC_App();
205
-		$apps = $appClass->listAllApps();
206
-		foreach ($apps as $app) {
207
-			$app['installed'] = true;
208
-			$this->allApps[$app['id']] = $app;
209
-		}
210
-
211
-		$apps = $this->getAppsForCategory('');
212
-		foreach ($apps as $app) {
213
-			$app['appstore'] = true;
214
-			if (!array_key_exists($app['id'], $this->allApps)) {
215
-				$this->allApps[$app['id']] = $app;
216
-			} else {
217
-				$this->allApps[$app['id']] = array_merge($this->allApps[$app['id']], $app);
218
-			}
219
-		}
220
-
221
-		// add bundle information
222
-		$bundles = $this->bundleFetcher->getBundles();
223
-		foreach($bundles as $bundle) {
224
-			foreach($bundle->getAppIdentifiers() as $identifier) {
225
-				foreach($this->allApps as &$app) {
226
-					if($app['id'] === $identifier) {
227
-						$app['bundleId'] = $bundle->getIdentifier();
228
-						continue;
229
-					}
230
-				}
231
-			}
232
-		}
233
-	}
234
-
235
-	private function getAllApps() {
236
-		return $this->allApps;
237
-	}
238
-	/**
239
-	 * Get all available apps in a category
240
-	 *
241
-	 * @param string $category
242
-	 * @return JSONResponse
243
-	 * @throws \Exception
244
-	 */
245
-	public function listApps(): JSONResponse {
246
-
247
-		$this->fetchApps();
248
-		$apps = $this->getAllApps();
249
-
250
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
251
-
252
-		// Extend existing app details
253
-		$apps = array_map(function($appData) use ($dependencyAnalyzer) {
254
-			$appstoreData = $appData['appstoreData'];
255
-			$appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($appstoreData['screenshots'][0]['url']) : '';
256
-
257
-			$newVersion = $this->installer->isUpdateAvailable($appData['id']);
258
-			if($newVersion && $this->appManager->isInstalled($appData['id'])) {
259
-				$appData['update'] = $newVersion;
260
-			}
261
-
262
-			// fix groups to be an array
263
-			$groups = array();
264
-			if (is_string($appData['groups'])) {
265
-				$groups = json_decode($appData['groups']);
266
-			}
267
-			$appData['groups'] = $groups;
268
-			$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
269
-
270
-			// fix licence vs license
271
-			if (isset($appData['license']) && !isset($appData['licence'])) {
272
-				$appData['licence'] = $appData['license'];
273
-			}
274
-
275
-			// analyse dependencies
276
-			$missing = $dependencyAnalyzer->analyze($appData);
277
-			$appData['canInstall'] = empty($missing);
278
-			$appData['missingDependencies'] = $missing;
279
-
280
-			$appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
281
-			$appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
282
-
283
-			return $appData;
284
-		}, $apps);
285
-
286
-		usort($apps, [$this, 'sortApps']);
287
-
288
-		return new JSONResponse(['apps' => $apps, 'status' => 'success']);
289
-	}
290
-
291
-	/**
292
-	 * Get all apps for a category from the app store
293
-	 *
294
-	 * @param string $requestedCategory
295
-	 * @return array
296
-	 * @throws \Exception
297
-	 */
298
-	private function getAppsForCategory($requestedCategory = ''): array {
299
-		$versionParser = new VersionParser();
300
-		$formattedApps = [];
301
-		$apps = $this->appFetcher->get();
302
-		foreach($apps as $app) {
303
-			// Skip all apps not in the requested category
304
-			if ($requestedCategory !== '') {
305
-				$isInCategory = false;
306
-				foreach($app['categories'] as $category) {
307
-					if($category === $requestedCategory) {
308
-						$isInCategory = true;
309
-					}
310
-				}
311
-				if(!$isInCategory) {
312
-					continue;
313
-				}
314
-			}
315
-
316
-			$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
317
-			$nextCloudVersionDependencies = [];
318
-			if($nextCloudVersion->getMinimumVersion() !== '') {
319
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
320
-			}
321
-			if($nextCloudVersion->getMaximumVersion() !== '') {
322
-				$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
323
-			}
324
-			$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
325
-			$existsLocally = \OC_App::getAppPath($app['id']) !== false;
326
-			$phpDependencies = [];
327
-			if($phpVersion->getMinimumVersion() !== '') {
328
-				$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
329
-			}
330
-			if($phpVersion->getMaximumVersion() !== '') {
331
-				$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
332
-			}
333
-			if(isset($app['releases'][0]['minIntSize'])) {
334
-				$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
335
-			}
336
-			$authors = '';
337
-			foreach($app['authors'] as $key => $author) {
338
-				$authors .= $author['name'];
339
-				if($key !== count($app['authors']) - 1) {
340
-					$authors .= ', ';
341
-				}
342
-			}
343
-
344
-			$currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
345
-			$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
346
-			$groups = null;
347
-			if($enabledValue !== 'no' && $enabledValue !== 'yes') {
348
-				$groups = $enabledValue;
349
-			}
350
-
351
-			$currentVersion = '';
352
-			if($this->appManager->isInstalled($app['id'])) {
353
-				$currentVersion = $this->appManager->getAppVersion($app['id']);
354
-			} else {
355
-				$currentLanguage = $app['releases'][0]['version'];
356
-			}
357
-
358
-			$formattedApps[] = [
359
-				'id' => $app['id'],
360
-				'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
361
-				'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
362
-				'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
363
-				'license' => $app['releases'][0]['licenses'],
364
-				'author' => $authors,
365
-				'shipped' => false,
366
-				'version' => $currentVersion,
367
-				'default_enable' => '',
368
-				'types' => [],
369
-				'documentation' => [
370
-					'admin' => $app['adminDocs'],
371
-					'user' => $app['userDocs'],
372
-					'developer' => $app['developerDocs']
373
-				],
374
-				'website' => $app['website'],
375
-				'bugs' => $app['issueTracker'],
376
-				'detailpage' => $app['website'],
377
-				'dependencies' => array_merge(
378
-					$nextCloudVersionDependencies,
379
-					$phpDependencies
380
-				),
381
-				'level' => ($app['isFeatured'] === true) ? 200 : 100,
382
-				'missingMaxOwnCloudVersion' => false,
383
-				'missingMinOwnCloudVersion' => false,
384
-				'canInstall' => true,
385
-				'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
386
-				'score' => $app['ratingOverall'],
387
-				'ratingNumOverall' => $app['ratingNumOverall'],
388
-				'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
389
-				'removable' => $existsLocally,
390
-				'active' => $this->appManager->isEnabledForUser($app['id']),
391
-				'needsDownload' => !$existsLocally,
392
-				'groups' => $groups,
393
-				'fromAppStore' => true,
394
-				'appstoreData' => $app,
395
-			];
396
-		}
397
-
398
-		return $formattedApps;
399
-	}
400
-
401
-	/**
402
-	 * @PasswordConfirmationRequired
403
-	 *
404
-	 * @param string $appId
405
-	 * @param array $groups
406
-	 * @return JSONResponse
407
-	 */
408
-	public function enableApp(string $appId, array $groups = []): JSONResponse {
409
-		return $this->enableApps([$appId], $groups);
410
-	}
411
-
412
-	/**
413
-	 * Enable one or more apps
414
-	 *
415
-	 * apps will be enabled for specific groups only if $groups is defined
416
-	 *
417
-	 * @PasswordConfirmationRequired
418
-	 * @param array $appIds
419
-	 * @param array $groups
420
-	 * @return JSONResponse
421
-	 */
422
-	public function enableApps(array $appIds, array $groups = []): JSONResponse {
423
-		try {
424
-			$updateRequired = false;
425
-
426
-			foreach ($appIds as $appId) {
427
-				$appId = OC_App::cleanAppId($appId);
428
-
429
-				// Check if app is already downloaded
430
-				/** @var Installer $installer */
431
-				$installer = \OC::$server->query(Installer::class);
432
-				$isDownloaded = $installer->isDownloaded($appId);
433
-
434
-				if(!$isDownloaded) {
435
-					$installer->downloadApp($appId);
436
-				}
437
-
438
-				$installer->installApp($appId);
439
-
440
-				if (count($groups) > 0) {
441
-					$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
442
-				} else {
443
-					$this->appManager->enableApp($appId);
444
-				}
445
-				if (\OC_App::shouldUpgrade($appId)) {
446
-					$updateRequired = true;
447
-				}
448
-			}
449
-			return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
450
-
451
-		} catch (\Exception $e) {
452
-			$this->logger->logException($e);
453
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
454
-		}
455
-	}
456
-
457
-	private function getGroupList(array $groups) {
458
-		$groupManager = \OC::$server->getGroupManager();
459
-		$groupsList = [];
460
-		foreach ($groups as $group) {
461
-			$groupItem = $groupManager->get($group);
462
-			if ($groupItem instanceof \OCP\IGroup) {
463
-				$groupsList[] = $groupManager->get($group);
464
-			}
465
-		}
466
-		return $groupsList;
467
-	}
468
-
469
-	/**
470
-	 * @PasswordConfirmationRequired
471
-	 *
472
-	 * @param string $appId
473
-	 * @return JSONResponse
474
-	 */
475
-	public function disableApp(string $appId): JSONResponse {
476
-		return $this->disableApps([$appId]);
477
-	}
478
-
479
-	/**
480
-	 * @PasswordConfirmationRequired
481
-	 *
482
-	 * @param array $appIds
483
-	 * @return JSONResponse
484
-	 */
485
-	public function disableApps(array $appIds): JSONResponse {
486
-		try {
487
-			foreach ($appIds as $appId) {
488
-				$appId = OC_App::cleanAppId($appId);
489
-				$this->appManager->disableApp($appId);
490
-			}
491
-			return new JSONResponse([]);
492
-		} catch (\Exception $e) {
493
-			$this->logger->logException($e);
494
-			return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
495
-		}
496
-	}
497
-
498
-	/**
499
-	 * @PasswordConfirmationRequired
500
-	 *
501
-	 * @param string $appId
502
-	 * @return JSONResponse
503
-	 */
504
-	public function uninstallApp(string $appId): JSONResponse {
505
-		$appId = OC_App::cleanAppId($appId);
506
-		$result = $this->installer->removeApp($appId);
507
-		if($result !== false) {
508
-			$this->appManager->clearAppsCache();
509
-			return new JSONResponse(['data' => ['appid' => $appId]]);
510
-		}
511
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
512
-	}
513
-
514
-	/**
515
-	 * @param string $appId
516
-	 * @return JSONResponse
517
-	 */
518
-	public function updateApp(string $appId): JSONResponse {
519
-		$appId = OC_App::cleanAppId($appId);
520
-
521
-		$this->config->setSystemValue('maintenance', true);
522
-		try {
523
-			$result = $this->installer->updateAppstoreApp($appId);
524
-			$this->config->setSystemValue('maintenance', false);
525
-		} catch (\Exception $ex) {
526
-			$this->config->setSystemValue('maintenance', false);
527
-			return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
528
-		}
529
-
530
-		if ($result !== false) {
531
-			return new JSONResponse(['data' => ['appid' => $appId]]);
532
-		}
533
-		return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
534
-	}
535
-
536
-	private function sortApps($a, $b) {
537
-		$a = (string)$a['name'];
538
-		$b = (string)$b['name'];
539
-		if ($a === $b) {
540
-			return 0;
541
-		}
542
-		return ($a < $b) ? -1 : 1;
543
-	}
61
+    /** @var \OCP\IL10N */
62
+    private $l10n;
63
+    /** @var IConfig */
64
+    private $config;
65
+    /** @var INavigationManager */
66
+    private $navigationManager;
67
+    /** @var IAppManager */
68
+    private $appManager;
69
+    /** @var CategoryFetcher */
70
+    private $categoryFetcher;
71
+    /** @var AppFetcher */
72
+    private $appFetcher;
73
+    /** @var IFactory */
74
+    private $l10nFactory;
75
+    /** @var BundleFetcher */
76
+    private $bundleFetcher;
77
+    /** @var Installer */
78
+    private $installer;
79
+    /** @var IURLGenerator */
80
+    private $urlGenerator;
81
+    /** @var ILogger */
82
+    private $logger;
83
+
84
+    /** @var array */
85
+    private $allApps = [];
86
+
87
+    /**
88
+     * @param string $appName
89
+     * @param IRequest $request
90
+     * @param IL10N $l10n
91
+     * @param IConfig $config
92
+     * @param INavigationManager $navigationManager
93
+     * @param IAppManager $appManager
94
+     * @param CategoryFetcher $categoryFetcher
95
+     * @param AppFetcher $appFetcher
96
+     * @param IFactory $l10nFactory
97
+     * @param BundleFetcher $bundleFetcher
98
+     * @param Installer $installer
99
+     * @param IURLGenerator $urlGenerator
100
+     * @param ILogger $logger
101
+     */
102
+    public function __construct(string $appName,
103
+                                IRequest $request,
104
+                                IL10N $l10n,
105
+                                IConfig $config,
106
+                                INavigationManager $navigationManager,
107
+                                IAppManager $appManager,
108
+                                CategoryFetcher $categoryFetcher,
109
+                                AppFetcher $appFetcher,
110
+                                IFactory $l10nFactory,
111
+                                BundleFetcher $bundleFetcher,
112
+                                Installer $installer,
113
+                                IURLGenerator $urlGenerator,
114
+                                ILogger $logger) {
115
+        parent::__construct($appName, $request);
116
+        $this->l10n = $l10n;
117
+        $this->config = $config;
118
+        $this->navigationManager = $navigationManager;
119
+        $this->appManager = $appManager;
120
+        $this->categoryFetcher = $categoryFetcher;
121
+        $this->appFetcher = $appFetcher;
122
+        $this->l10nFactory = $l10nFactory;
123
+        $this->bundleFetcher = $bundleFetcher;
124
+        $this->installer = $installer;
125
+        $this->urlGenerator = $urlGenerator;
126
+        $this->logger = $logger;
127
+    }
128
+
129
+    /**
130
+     * @NoCSRFRequired
131
+     *
132
+     * @return TemplateResponse
133
+     */
134
+    public function viewApps(): TemplateResponse {
135
+        \OC_Util::addScript('settings', 'apps');
136
+        \OC_Util::addVendorScript('core', 'marked/marked.min');
137
+        $params = [];
138
+        $params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
139
+        $params['updateCount'] = count($this->getAppsWithUpdates());
140
+        $params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
141
+        $params['bundles'] = $this->getBundles();
142
+        $this->navigationManager->setActiveEntry('core_apps');
143
+
144
+        $templateResponse = new TemplateResponse('settings', 'settings', ['serverData' => $params]);
145
+        $policy = new ContentSecurityPolicy();
146
+        $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
147
+        $templateResponse->setContentSecurityPolicy($policy);
148
+
149
+        return $templateResponse;
150
+    }
151
+
152
+    private function getAppsWithUpdates() {
153
+        $appClass = new \OC_App();
154
+        $apps = $appClass->listAllApps();
155
+        foreach($apps as $key => $app) {
156
+            $newVersion = $this->installer->isUpdateAvailable($app['id']);
157
+            if($newVersion === false) {
158
+                unset($apps[$key]);
159
+            }
160
+        }
161
+        return $apps;
162
+    }
163
+
164
+    private function getBundles() {
165
+        $result = [];
166
+        $bundles = $this->bundleFetcher->getBundles();
167
+        foreach ($bundles as $bundle) {
168
+            $result[] = [
169
+                'name' => $bundle->getName(),
170
+                'id' => $bundle->getIdentifier(),
171
+                'appIdentifiers' => $bundle->getAppIdentifiers()
172
+            ];
173
+        }
174
+        return $result;
175
+
176
+    }
177
+
178
+    /**
179
+     * Get all available categories
180
+     *
181
+     * @return JSONResponse
182
+     */
183
+    public function listCategories(): JSONResponse {
184
+        return new JSONResponse($this->getAllCategories());
185
+    }
186
+
187
+    private function getAllCategories() {
188
+        $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
189
+
190
+        $formattedCategories = [];
191
+        $categories = $this->categoryFetcher->get();
192
+        foreach($categories as $category) {
193
+            $formattedCategories[] = [
194
+                'id' => $category['id'],
195
+                'ident' => $category['id'],
196
+                'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
197
+            ];
198
+        }
199
+
200
+        return $formattedCategories;
201
+    }
202
+
203
+    private function fetchApps() {
204
+        $appClass = new \OC_App();
205
+        $apps = $appClass->listAllApps();
206
+        foreach ($apps as $app) {
207
+            $app['installed'] = true;
208
+            $this->allApps[$app['id']] = $app;
209
+        }
210
+
211
+        $apps = $this->getAppsForCategory('');
212
+        foreach ($apps as $app) {
213
+            $app['appstore'] = true;
214
+            if (!array_key_exists($app['id'], $this->allApps)) {
215
+                $this->allApps[$app['id']] = $app;
216
+            } else {
217
+                $this->allApps[$app['id']] = array_merge($this->allApps[$app['id']], $app);
218
+            }
219
+        }
220
+
221
+        // add bundle information
222
+        $bundles = $this->bundleFetcher->getBundles();
223
+        foreach($bundles as $bundle) {
224
+            foreach($bundle->getAppIdentifiers() as $identifier) {
225
+                foreach($this->allApps as &$app) {
226
+                    if($app['id'] === $identifier) {
227
+                        $app['bundleId'] = $bundle->getIdentifier();
228
+                        continue;
229
+                    }
230
+                }
231
+            }
232
+        }
233
+    }
234
+
235
+    private function getAllApps() {
236
+        return $this->allApps;
237
+    }
238
+    /**
239
+     * Get all available apps in a category
240
+     *
241
+     * @param string $category
242
+     * @return JSONResponse
243
+     * @throws \Exception
244
+     */
245
+    public function listApps(): JSONResponse {
246
+
247
+        $this->fetchApps();
248
+        $apps = $this->getAllApps();
249
+
250
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
251
+
252
+        // Extend existing app details
253
+        $apps = array_map(function($appData) use ($dependencyAnalyzer) {
254
+            $appstoreData = $appData['appstoreData'];
255
+            $appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($appstoreData['screenshots'][0]['url']) : '';
256
+
257
+            $newVersion = $this->installer->isUpdateAvailable($appData['id']);
258
+            if($newVersion && $this->appManager->isInstalled($appData['id'])) {
259
+                $appData['update'] = $newVersion;
260
+            }
261
+
262
+            // fix groups to be an array
263
+            $groups = array();
264
+            if (is_string($appData['groups'])) {
265
+                $groups = json_decode($appData['groups']);
266
+            }
267
+            $appData['groups'] = $groups;
268
+            $appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
269
+
270
+            // fix licence vs license
271
+            if (isset($appData['license']) && !isset($appData['licence'])) {
272
+                $appData['licence'] = $appData['license'];
273
+            }
274
+
275
+            // analyse dependencies
276
+            $missing = $dependencyAnalyzer->analyze($appData);
277
+            $appData['canInstall'] = empty($missing);
278
+            $appData['missingDependencies'] = $missing;
279
+
280
+            $appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
281
+            $appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
282
+
283
+            return $appData;
284
+        }, $apps);
285
+
286
+        usort($apps, [$this, 'sortApps']);
287
+
288
+        return new JSONResponse(['apps' => $apps, 'status' => 'success']);
289
+    }
290
+
291
+    /**
292
+     * Get all apps for a category from the app store
293
+     *
294
+     * @param string $requestedCategory
295
+     * @return array
296
+     * @throws \Exception
297
+     */
298
+    private function getAppsForCategory($requestedCategory = ''): array {
299
+        $versionParser = new VersionParser();
300
+        $formattedApps = [];
301
+        $apps = $this->appFetcher->get();
302
+        foreach($apps as $app) {
303
+            // Skip all apps not in the requested category
304
+            if ($requestedCategory !== '') {
305
+                $isInCategory = false;
306
+                foreach($app['categories'] as $category) {
307
+                    if($category === $requestedCategory) {
308
+                        $isInCategory = true;
309
+                    }
310
+                }
311
+                if(!$isInCategory) {
312
+                    continue;
313
+                }
314
+            }
315
+
316
+            $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
317
+            $nextCloudVersionDependencies = [];
318
+            if($nextCloudVersion->getMinimumVersion() !== '') {
319
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
320
+            }
321
+            if($nextCloudVersion->getMaximumVersion() !== '') {
322
+                $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
323
+            }
324
+            $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
325
+            $existsLocally = \OC_App::getAppPath($app['id']) !== false;
326
+            $phpDependencies = [];
327
+            if($phpVersion->getMinimumVersion() !== '') {
328
+                $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
329
+            }
330
+            if($phpVersion->getMaximumVersion() !== '') {
331
+                $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
332
+            }
333
+            if(isset($app['releases'][0]['minIntSize'])) {
334
+                $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
335
+            }
336
+            $authors = '';
337
+            foreach($app['authors'] as $key => $author) {
338
+                $authors .= $author['name'];
339
+                if($key !== count($app['authors']) - 1) {
340
+                    $authors .= ', ';
341
+                }
342
+            }
343
+
344
+            $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
345
+            $enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
346
+            $groups = null;
347
+            if($enabledValue !== 'no' && $enabledValue !== 'yes') {
348
+                $groups = $enabledValue;
349
+            }
350
+
351
+            $currentVersion = '';
352
+            if($this->appManager->isInstalled($app['id'])) {
353
+                $currentVersion = $this->appManager->getAppVersion($app['id']);
354
+            } else {
355
+                $currentLanguage = $app['releases'][0]['version'];
356
+            }
357
+
358
+            $formattedApps[] = [
359
+                'id' => $app['id'],
360
+                'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
361
+                'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
362
+                'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
363
+                'license' => $app['releases'][0]['licenses'],
364
+                'author' => $authors,
365
+                'shipped' => false,
366
+                'version' => $currentVersion,
367
+                'default_enable' => '',
368
+                'types' => [],
369
+                'documentation' => [
370
+                    'admin' => $app['adminDocs'],
371
+                    'user' => $app['userDocs'],
372
+                    'developer' => $app['developerDocs']
373
+                ],
374
+                'website' => $app['website'],
375
+                'bugs' => $app['issueTracker'],
376
+                'detailpage' => $app['website'],
377
+                'dependencies' => array_merge(
378
+                    $nextCloudVersionDependencies,
379
+                    $phpDependencies
380
+                ),
381
+                'level' => ($app['isFeatured'] === true) ? 200 : 100,
382
+                'missingMaxOwnCloudVersion' => false,
383
+                'missingMinOwnCloudVersion' => false,
384
+                'canInstall' => true,
385
+                'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
386
+                'score' => $app['ratingOverall'],
387
+                'ratingNumOverall' => $app['ratingNumOverall'],
388
+                'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
389
+                'removable' => $existsLocally,
390
+                'active' => $this->appManager->isEnabledForUser($app['id']),
391
+                'needsDownload' => !$existsLocally,
392
+                'groups' => $groups,
393
+                'fromAppStore' => true,
394
+                'appstoreData' => $app,
395
+            ];
396
+        }
397
+
398
+        return $formattedApps;
399
+    }
400
+
401
+    /**
402
+     * @PasswordConfirmationRequired
403
+     *
404
+     * @param string $appId
405
+     * @param array $groups
406
+     * @return JSONResponse
407
+     */
408
+    public function enableApp(string $appId, array $groups = []): JSONResponse {
409
+        return $this->enableApps([$appId], $groups);
410
+    }
411
+
412
+    /**
413
+     * Enable one or more apps
414
+     *
415
+     * apps will be enabled for specific groups only if $groups is defined
416
+     *
417
+     * @PasswordConfirmationRequired
418
+     * @param array $appIds
419
+     * @param array $groups
420
+     * @return JSONResponse
421
+     */
422
+    public function enableApps(array $appIds, array $groups = []): JSONResponse {
423
+        try {
424
+            $updateRequired = false;
425
+
426
+            foreach ($appIds as $appId) {
427
+                $appId = OC_App::cleanAppId($appId);
428
+
429
+                // Check if app is already downloaded
430
+                /** @var Installer $installer */
431
+                $installer = \OC::$server->query(Installer::class);
432
+                $isDownloaded = $installer->isDownloaded($appId);
433
+
434
+                if(!$isDownloaded) {
435
+                    $installer->downloadApp($appId);
436
+                }
437
+
438
+                $installer->installApp($appId);
439
+
440
+                if (count($groups) > 0) {
441
+                    $this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
442
+                } else {
443
+                    $this->appManager->enableApp($appId);
444
+                }
445
+                if (\OC_App::shouldUpgrade($appId)) {
446
+                    $updateRequired = true;
447
+                }
448
+            }
449
+            return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
450
+
451
+        } catch (\Exception $e) {
452
+            $this->logger->logException($e);
453
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
454
+        }
455
+    }
456
+
457
+    private function getGroupList(array $groups) {
458
+        $groupManager = \OC::$server->getGroupManager();
459
+        $groupsList = [];
460
+        foreach ($groups as $group) {
461
+            $groupItem = $groupManager->get($group);
462
+            if ($groupItem instanceof \OCP\IGroup) {
463
+                $groupsList[] = $groupManager->get($group);
464
+            }
465
+        }
466
+        return $groupsList;
467
+    }
468
+
469
+    /**
470
+     * @PasswordConfirmationRequired
471
+     *
472
+     * @param string $appId
473
+     * @return JSONResponse
474
+     */
475
+    public function disableApp(string $appId): JSONResponse {
476
+        return $this->disableApps([$appId]);
477
+    }
478
+
479
+    /**
480
+     * @PasswordConfirmationRequired
481
+     *
482
+     * @param array $appIds
483
+     * @return JSONResponse
484
+     */
485
+    public function disableApps(array $appIds): JSONResponse {
486
+        try {
487
+            foreach ($appIds as $appId) {
488
+                $appId = OC_App::cleanAppId($appId);
489
+                $this->appManager->disableApp($appId);
490
+            }
491
+            return new JSONResponse([]);
492
+        } catch (\Exception $e) {
493
+            $this->logger->logException($e);
494
+            return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
495
+        }
496
+    }
497
+
498
+    /**
499
+     * @PasswordConfirmationRequired
500
+     *
501
+     * @param string $appId
502
+     * @return JSONResponse
503
+     */
504
+    public function uninstallApp(string $appId): JSONResponse {
505
+        $appId = OC_App::cleanAppId($appId);
506
+        $result = $this->installer->removeApp($appId);
507
+        if($result !== false) {
508
+            $this->appManager->clearAppsCache();
509
+            return new JSONResponse(['data' => ['appid' => $appId]]);
510
+        }
511
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
512
+    }
513
+
514
+    /**
515
+     * @param string $appId
516
+     * @return JSONResponse
517
+     */
518
+    public function updateApp(string $appId): JSONResponse {
519
+        $appId = OC_App::cleanAppId($appId);
520
+
521
+        $this->config->setSystemValue('maintenance', true);
522
+        try {
523
+            $result = $this->installer->updateAppstoreApp($appId);
524
+            $this->config->setSystemValue('maintenance', false);
525
+        } catch (\Exception $ex) {
526
+            $this->config->setSystemValue('maintenance', false);
527
+            return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
528
+        }
529
+
530
+        if ($result !== false) {
531
+            return new JSONResponse(['data' => ['appid' => $appId]]);
532
+        }
533
+        return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
534
+    }
535
+
536
+    private function sortApps($a, $b) {
537
+        $a = (string)$a['name'];
538
+        $b = (string)$b['name'];
539
+        if ($a === $b) {
540
+            return 0;
541
+        }
542
+        return ($a < $b) ? -1 : 1;
543
+    }
544 544
 
545 545
 }
Please login to merge, or discard this patch.
lib/public/App/IAppManager.php 1 patch
Indentation   +121 added lines, -121 removed lines patch added patch discarded remove patch
@@ -37,125 +37,125 @@
 block discarded – undo
37 37
  */
38 38
 interface IAppManager {
39 39
 
40
-	/**
41
-	 * Returns the app information from "appinfo/info.xml".
42
-	 *
43
-	 * @param string $appId
44
-	 * @return mixed
45
-	 * @since 14.0.0
46
-	 */
47
-	public function getAppInfo(string $appId, bool $path = false, $lang = null);
48
-
49
-	/**
50
-	 * Returns the app information from "appinfo/info.xml".
51
-	 *
52
-	 * @param string $appId
53
-	 * @param bool $useCache
54
-	 * @return string
55
-	 * @since 14.0.0
56
-	 */
57
-	public function getAppVersion(string $appId, bool $useCache = true): string;
58
-
59
-	/**
60
-	 * Check if an app is enabled for user
61
-	 *
62
-	 * @param string $appId
63
-	 * @param \OCP\IUser $user (optional) if not defined, the currently loggedin user will be used
64
-	 * @return bool
65
-	 * @since 8.0.0
66
-	 */
67
-	public function isEnabledForUser($appId, $user = null);
68
-
69
-	/**
70
-	 * Check if an app is enabled in the instance
71
-	 *
72
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
73
-	 *
74
-	 * @param string $appId
75
-	 * @return bool
76
-	 * @since 8.0.0
77
-	 */
78
-	public function isInstalled($appId);
79
-
80
-	/**
81
-	 * Enable an app for every user
82
-	 *
83
-	 * @param string $appId
84
-	 * @throws AppPathNotFoundException
85
-	 * @since 8.0.0
86
-	 */
87
-	public function enableApp($appId);
88
-
89
-	/**
90
-	 * Whether a list of types contains a protected app type
91
-	 *
92
-	 * @param string[] $types
93
-	 * @return bool
94
-	 * @since 12.0.0
95
-	 */
96
-	public function hasProtectedAppType($types);
97
-
98
-	/**
99
-	 * Enable an app only for specific groups
100
-	 *
101
-	 * @param string $appId
102
-	 * @param \OCP\IGroup[] $groups
103
-	 * @throws \Exception
104
-	 * @since 8.0.0
105
-	 */
106
-	public function enableAppForGroups($appId, $groups);
107
-
108
-	/**
109
-	 * Disable an app for every user
110
-	 *
111
-	 * @param string $appId
112
-	 * @since 8.0.0
113
-	 */
114
-	public function disableApp($appId);
115
-
116
-	/**
117
-	 * Get the directory for the given app.
118
-	 *
119
-	 * @param string $appId
120
-	 * @return string
121
-	 * @since 11.0.0
122
-	 * @throws AppPathNotFoundException
123
-	 */
124
-	public function getAppPath($appId);
125
-
126
-	/**
127
-	 * List all apps enabled for a user
128
-	 *
129
-	 * @param \OCP\IUser $user
130
-	 * @return string[]
131
-	 * @since 8.1.0
132
-	 */
133
-	public function getEnabledAppsForUser(IUser $user);
134
-
135
-	/**
136
-	 * List all installed apps
137
-	 *
138
-	 * @return string[]
139
-	 * @since 8.1.0
140
-	 */
141
-	public function getInstalledApps();
142
-
143
-	/**
144
-	 * Clear the cached list of apps when enabling/disabling an app
145
-	 * @since 8.1.0
146
-	 */
147
-	public function clearAppsCache();
148
-
149
-	/**
150
-	 * @param string $appId
151
-	 * @return boolean
152
-	 * @since 9.0.0
153
-	 */
154
-	public function isShipped($appId);
155
-
156
-	/**
157
-	 * @return string[]
158
-	 * @since 9.0.0
159
-	 */
160
-	public function getAlwaysEnabledApps();
40
+    /**
41
+     * Returns the app information from "appinfo/info.xml".
42
+     *
43
+     * @param string $appId
44
+     * @return mixed
45
+     * @since 14.0.0
46
+     */
47
+    public function getAppInfo(string $appId, bool $path = false, $lang = null);
48
+
49
+    /**
50
+     * Returns the app information from "appinfo/info.xml".
51
+     *
52
+     * @param string $appId
53
+     * @param bool $useCache
54
+     * @return string
55
+     * @since 14.0.0
56
+     */
57
+    public function getAppVersion(string $appId, bool $useCache = true): string;
58
+
59
+    /**
60
+     * Check if an app is enabled for user
61
+     *
62
+     * @param string $appId
63
+     * @param \OCP\IUser $user (optional) if not defined, the currently loggedin user will be used
64
+     * @return bool
65
+     * @since 8.0.0
66
+     */
67
+    public function isEnabledForUser($appId, $user = null);
68
+
69
+    /**
70
+     * Check if an app is enabled in the instance
71
+     *
72
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
73
+     *
74
+     * @param string $appId
75
+     * @return bool
76
+     * @since 8.0.0
77
+     */
78
+    public function isInstalled($appId);
79
+
80
+    /**
81
+     * Enable an app for every user
82
+     *
83
+     * @param string $appId
84
+     * @throws AppPathNotFoundException
85
+     * @since 8.0.0
86
+     */
87
+    public function enableApp($appId);
88
+
89
+    /**
90
+     * Whether a list of types contains a protected app type
91
+     *
92
+     * @param string[] $types
93
+     * @return bool
94
+     * @since 12.0.0
95
+     */
96
+    public function hasProtectedAppType($types);
97
+
98
+    /**
99
+     * Enable an app only for specific groups
100
+     *
101
+     * @param string $appId
102
+     * @param \OCP\IGroup[] $groups
103
+     * @throws \Exception
104
+     * @since 8.0.0
105
+     */
106
+    public function enableAppForGroups($appId, $groups);
107
+
108
+    /**
109
+     * Disable an app for every user
110
+     *
111
+     * @param string $appId
112
+     * @since 8.0.0
113
+     */
114
+    public function disableApp($appId);
115
+
116
+    /**
117
+     * Get the directory for the given app.
118
+     *
119
+     * @param string $appId
120
+     * @return string
121
+     * @since 11.0.0
122
+     * @throws AppPathNotFoundException
123
+     */
124
+    public function getAppPath($appId);
125
+
126
+    /**
127
+     * List all apps enabled for a user
128
+     *
129
+     * @param \OCP\IUser $user
130
+     * @return string[]
131
+     * @since 8.1.0
132
+     */
133
+    public function getEnabledAppsForUser(IUser $user);
134
+
135
+    /**
136
+     * List all installed apps
137
+     *
138
+     * @return string[]
139
+     * @since 8.1.0
140
+     */
141
+    public function getInstalledApps();
142
+
143
+    /**
144
+     * Clear the cached list of apps when enabling/disabling an app
145
+     * @since 8.1.0
146
+     */
147
+    public function clearAppsCache();
148
+
149
+    /**
150
+     * @param string $appId
151
+     * @return boolean
152
+     * @since 9.0.0
153
+     */
154
+    public function isShipped($appId);
155
+
156
+    /**
157
+     * @return string[]
158
+     * @since 9.0.0
159
+     */
160
+    public function getAlwaysEnabledApps();
161 161
 }
Please login to merge, or discard this patch.
lib/private/App/AppManager.php 1 patch
Indentation   +414 added lines, -414 removed lines patch added patch discarded remove patch
@@ -44,418 +44,418 @@
 block discarded – undo
44 44
 
45 45
 class AppManager implements IAppManager {
46 46
 
47
-	/**
48
-	 * Apps with these types can not be enabled for certain groups only
49
-	 * @var string[]
50
-	 */
51
-	protected $protectedAppTypes = [
52
-		'filesystem',
53
-		'prelogin',
54
-		'authentication',
55
-		'logging',
56
-		'prevent_group_restriction',
57
-	];
58
-
59
-	/** @var IUserSession */
60
-	private $userSession;
61
-
62
-	/** @var AppConfig */
63
-	private $appConfig;
64
-
65
-	/** @var IGroupManager */
66
-	private $groupManager;
67
-
68
-	/** @var ICacheFactory */
69
-	private $memCacheFactory;
70
-
71
-	/** @var EventDispatcherInterface */
72
-	private $dispatcher;
73
-
74
-	/** @var string[] $appId => $enabled */
75
-	private $installedAppsCache;
76
-
77
-	/** @var string[] */
78
-	private $shippedApps;
79
-
80
-	/** @var string[] */
81
-	private $alwaysEnabled;
82
-
83
-	/** @var array */
84
-	private $appInfos = [];
85
-
86
-	/** @var array */
87
-	private $appVersions = [];
88
-
89
-	/**
90
-	 * @param IUserSession $userSession
91
-	 * @param AppConfig $appConfig
92
-	 * @param IGroupManager $groupManager
93
-	 * @param ICacheFactory $memCacheFactory
94
-	 * @param EventDispatcherInterface $dispatcher
95
-	 */
96
-	public function __construct(IUserSession $userSession,
97
-								AppConfig $appConfig,
98
-								IGroupManager $groupManager,
99
-								ICacheFactory $memCacheFactory,
100
-								EventDispatcherInterface $dispatcher) {
101
-		$this->userSession = $userSession;
102
-		$this->appConfig = $appConfig;
103
-		$this->groupManager = $groupManager;
104
-		$this->memCacheFactory = $memCacheFactory;
105
-		$this->dispatcher = $dispatcher;
106
-	}
107
-
108
-	/**
109
-	 * @return string[] $appId => $enabled
110
-	 */
111
-	private function getInstalledAppsValues() {
112
-		if (!$this->installedAppsCache) {
113
-			$values = $this->appConfig->getValues(false, 'enabled');
114
-
115
-			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
116
-			foreach($alwaysEnabledApps as $appId) {
117
-				$values[$appId] = 'yes';
118
-			}
119
-
120
-			$this->installedAppsCache = array_filter($values, function ($value) {
121
-				return $value !== 'no';
122
-			});
123
-			ksort($this->installedAppsCache);
124
-		}
125
-		return $this->installedAppsCache;
126
-	}
127
-
128
-	/**
129
-	 * List all installed apps
130
-	 *
131
-	 * @return string[]
132
-	 */
133
-	public function getInstalledApps() {
134
-		return array_keys($this->getInstalledAppsValues());
135
-	}
136
-
137
-	/**
138
-	 * List all apps enabled for a user
139
-	 *
140
-	 * @param \OCP\IUser $user
141
-	 * @return string[]
142
-	 */
143
-	public function getEnabledAppsForUser(IUser $user) {
144
-		$apps = $this->getInstalledAppsValues();
145
-		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
146
-			return $this->checkAppForUser($enabled, $user);
147
-		});
148
-		return array_keys($appsForUser);
149
-	}
150
-
151
-	/**
152
-	 * Check if an app is enabled for user
153
-	 *
154
-	 * @param string $appId
155
-	 * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
156
-	 * @return bool
157
-	 */
158
-	public function isEnabledForUser($appId, $user = null) {
159
-		if ($this->isAlwaysEnabled($appId)) {
160
-			return true;
161
-		}
162
-		if ($user === null) {
163
-			$user = $this->userSession->getUser();
164
-		}
165
-		$installedApps = $this->getInstalledAppsValues();
166
-		if (isset($installedApps[$appId])) {
167
-			return $this->checkAppForUser($installedApps[$appId], $user);
168
-		} else {
169
-			return false;
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * @param string $enabled
175
-	 * @param IUser $user
176
-	 * @return bool
177
-	 */
178
-	private function checkAppForUser($enabled, $user) {
179
-		if ($enabled === 'yes') {
180
-			return true;
181
-		} elseif ($user === null) {
182
-			return false;
183
-		} else {
184
-			if(empty($enabled)){
185
-				return false;
186
-			}
187
-
188
-			$groupIds = json_decode($enabled);
189
-
190
-			if (!is_array($groupIds)) {
191
-				$jsonError = json_last_error();
192
-				\OC::$server->getLogger()->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
193
-				return false;
194
-			}
195
-
196
-			$userGroups = $this->groupManager->getUserGroupIds($user);
197
-			foreach ($userGroups as $groupId) {
198
-				if (in_array($groupId, $groupIds, true)) {
199
-					return true;
200
-				}
201
-			}
202
-			return false;
203
-		}
204
-	}
205
-
206
-	/**
207
-	 * Check if an app is enabled in the instance
208
-	 *
209
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
210
-	 *
211
-	 * @param string $appId
212
-	 * @return bool
213
-	 */
214
-	public function isInstalled($appId) {
215
-		$installedApps = $this->getInstalledAppsValues();
216
-		return isset($installedApps[$appId]);
217
-	}
218
-
219
-	/**
220
-	 * Enable an app for every user
221
-	 *
222
-	 * @param string $appId
223
-	 * @throws AppPathNotFoundException
224
-	 */
225
-	public function enableApp($appId) {
226
-		// Check if app exists
227
-		$this->getAppPath($appId);
228
-
229
-		$this->installedAppsCache[$appId] = 'yes';
230
-		$this->appConfig->setValue($appId, 'enabled', 'yes');
231
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
232
-			ManagerEvent::EVENT_APP_ENABLE, $appId
233
-		));
234
-		$this->clearAppsCache();
235
-	}
236
-
237
-	/**
238
-	 * Whether a list of types contains a protected app type
239
-	 *
240
-	 * @param string[] $types
241
-	 * @return bool
242
-	 */
243
-	public function hasProtectedAppType($types) {
244
-		if (empty($types)) {
245
-			return false;
246
-		}
247
-
248
-		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
249
-		return !empty($protectedTypes);
250
-	}
251
-
252
-	/**
253
-	 * Enable an app only for specific groups
254
-	 *
255
-	 * @param string $appId
256
-	 * @param \OCP\IGroup[] $groups
257
-	 * @throws \Exception if app can't be enabled for groups
258
-	 */
259
-	public function enableAppForGroups($appId, $groups) {
260
-		$info = $this->getAppInfo($appId);
261
-		if (!empty($info['types'])) {
262
-			$protectedTypes = array_intersect($this->protectedAppTypes, $info['types']);
263
-			if (!empty($protectedTypes)) {
264
-				throw new \Exception("$appId can't be enabled for groups.");
265
-			}
266
-		}
267
-
268
-		$groupIds = array_map(function ($group) {
269
-			/** @var \OCP\IGroup $group */
270
-			return $group->getGID();
271
-		}, $groups);
272
-		$this->installedAppsCache[$appId] = json_encode($groupIds);
273
-		$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
274
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
275
-			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
276
-		));
277
-		$this->clearAppsCache();
278
-	}
279
-
280
-	/**
281
-	 * Disable an app for every user
282
-	 *
283
-	 * @param string $appId
284
-	 * @throws \Exception if app can't be disabled
285
-	 */
286
-	public function disableApp($appId) {
287
-		if ($this->isAlwaysEnabled($appId)) {
288
-			throw new \Exception("$appId can't be disabled.");
289
-		}
290
-		unset($this->installedAppsCache[$appId]);
291
-		$this->appConfig->setValue($appId, 'enabled', 'no');
292
-
293
-		// run uninstall steps
294
-		$appData = $this->getAppInfo($appId);
295
-		if (!is_null($appData)) {
296
-			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
297
-		}
298
-
299
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
300
-			ManagerEvent::EVENT_APP_DISABLE, $appId
301
-		));
302
-		$this->clearAppsCache();
303
-	}
304
-
305
-	/**
306
-	 * Get the directory for the given app.
307
-	 *
308
-	 * @param string $appId
309
-	 * @return string
310
-	 * @throws AppPathNotFoundException if app folder can't be found
311
-	 */
312
-	public function getAppPath($appId) {
313
-		$appPath = \OC_App::getAppPath($appId);
314
-		if($appPath === false) {
315
-			throw new AppPathNotFoundException('Could not find path for ' . $appId);
316
-		}
317
-		return $appPath;
318
-	}
319
-
320
-	/**
321
-	 * Clear the cached list of apps when enabling/disabling an app
322
-	 */
323
-	public function clearAppsCache() {
324
-		$settingsMemCache = $this->memCacheFactory->createDistributed('settings');
325
-		$settingsMemCache->clear('listApps');
326
-		$this->appInfos = [];
327
-	}
328
-
329
-	/**
330
-	 * Returns a list of apps that need upgrade
331
-	 *
332
-	 * @param string $version Nextcloud version as array of version components
333
-	 * @return array list of app info from apps that need an upgrade
334
-	 *
335
-	 * @internal
336
-	 */
337
-	public function getAppsNeedingUpgrade($version) {
338
-		$appsToUpgrade = [];
339
-		$apps = $this->getInstalledApps();
340
-		foreach ($apps as $appId) {
341
-			$appInfo = $this->getAppInfo($appId);
342
-			$appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
343
-			if ($appDbVersion
344
-				&& isset($appInfo['version'])
345
-				&& version_compare($appInfo['version'], $appDbVersion, '>')
346
-				&& \OC_App::isAppCompatible($version, $appInfo)
347
-			) {
348
-				$appsToUpgrade[] = $appInfo;
349
-			}
350
-		}
351
-
352
-		return $appsToUpgrade;
353
-	}
354
-
355
-	/**
356
-	 * Returns the app information from "appinfo/info.xml".
357
-	 *
358
-	 * @param string $appId app id
359
-	 *
360
-	 * @param bool $path
361
-	 * @param null $lang
362
-	 * @return array|null app info
363
-	 */
364
-	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
365
-		if ($path) {
366
-			$file = $appId;
367
-		} else {
368
-			if ($lang === null && isset($this->appInfos[$appId])) {
369
-				return $this->appInfos[$appId];
370
-			}
371
-			try {
372
-				$appPath = $this->getAppPath($appId);
373
-			} catch (AppPathNotFoundException $e) {
374
-				return null;
375
-			}
376
-			$file = $appPath . '/appinfo/info.xml';
377
-		}
378
-
379
-		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
380
-		$data = $parser->parse($file);
381
-
382
-		if (is_array($data)) {
383
-			$data = \OC_App::parseAppInfo($data, $lang);
384
-		}
385
-
386
-		if ($lang === null) {
387
-			$this->appInfos[$appId] = $data;
388
-		}
389
-
390
-		return $data;
391
-	}
392
-
393
-	public function getAppVersion(string $appId, bool $useCache = true): string {
394
-		if(!$useCache || !isset($this->appVersions[$appId])) {
395
-			$appInfo = \OC::$server->getAppManager()->getAppInfo($appId);
396
-			$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
397
-		}
398
-		return $this->appVersions[$appId];
399
-	}
400
-
401
-	/**
402
-	 * Returns a list of apps incompatible with the given version
403
-	 *
404
-	 * @param string $version Nextcloud version as array of version components
405
-	 *
406
-	 * @return array list of app info from incompatible apps
407
-	 *
408
-	 * @internal
409
-	 */
410
-	public function getIncompatibleApps(string $version): array {
411
-		$apps = $this->getInstalledApps();
412
-		$incompatibleApps = array();
413
-		foreach ($apps as $appId) {
414
-			$info = $this->getAppInfo($appId);
415
-			if ($info === null) {
416
-				$incompatibleApps[] = ['id' => $appId];
417
-			} else if (!\OC_App::isAppCompatible($version, $info)) {
418
-				$incompatibleApps[] = $info;
419
-			}
420
-		}
421
-		return $incompatibleApps;
422
-	}
423
-
424
-	/**
425
-	 * @inheritdoc
426
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
427
-	 */
428
-	public function isShipped($appId) {
429
-		$this->loadShippedJson();
430
-		return in_array($appId, $this->shippedApps, true);
431
-	}
432
-
433
-	private function isAlwaysEnabled($appId) {
434
-		$alwaysEnabled = $this->getAlwaysEnabledApps();
435
-		return in_array($appId, $alwaysEnabled, true);
436
-	}
437
-
438
-	/**
439
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
440
-	 * @throws \Exception
441
-	 */
442
-	private function loadShippedJson() {
443
-		if ($this->shippedApps === null) {
444
-			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
445
-			if (!file_exists($shippedJson)) {
446
-				throw new \Exception("File not found: $shippedJson");
447
-			}
448
-			$content = json_decode(file_get_contents($shippedJson), true);
449
-			$this->shippedApps = $content['shippedApps'];
450
-			$this->alwaysEnabled = $content['alwaysEnabled'];
451
-		}
452
-	}
453
-
454
-	/**
455
-	 * @inheritdoc
456
-	 */
457
-	public function getAlwaysEnabledApps() {
458
-		$this->loadShippedJson();
459
-		return $this->alwaysEnabled;
460
-	}
47
+    /**
48
+     * Apps with these types can not be enabled for certain groups only
49
+     * @var string[]
50
+     */
51
+    protected $protectedAppTypes = [
52
+        'filesystem',
53
+        'prelogin',
54
+        'authentication',
55
+        'logging',
56
+        'prevent_group_restriction',
57
+    ];
58
+
59
+    /** @var IUserSession */
60
+    private $userSession;
61
+
62
+    /** @var AppConfig */
63
+    private $appConfig;
64
+
65
+    /** @var IGroupManager */
66
+    private $groupManager;
67
+
68
+    /** @var ICacheFactory */
69
+    private $memCacheFactory;
70
+
71
+    /** @var EventDispatcherInterface */
72
+    private $dispatcher;
73
+
74
+    /** @var string[] $appId => $enabled */
75
+    private $installedAppsCache;
76
+
77
+    /** @var string[] */
78
+    private $shippedApps;
79
+
80
+    /** @var string[] */
81
+    private $alwaysEnabled;
82
+
83
+    /** @var array */
84
+    private $appInfos = [];
85
+
86
+    /** @var array */
87
+    private $appVersions = [];
88
+
89
+    /**
90
+     * @param IUserSession $userSession
91
+     * @param AppConfig $appConfig
92
+     * @param IGroupManager $groupManager
93
+     * @param ICacheFactory $memCacheFactory
94
+     * @param EventDispatcherInterface $dispatcher
95
+     */
96
+    public function __construct(IUserSession $userSession,
97
+                                AppConfig $appConfig,
98
+                                IGroupManager $groupManager,
99
+                                ICacheFactory $memCacheFactory,
100
+                                EventDispatcherInterface $dispatcher) {
101
+        $this->userSession = $userSession;
102
+        $this->appConfig = $appConfig;
103
+        $this->groupManager = $groupManager;
104
+        $this->memCacheFactory = $memCacheFactory;
105
+        $this->dispatcher = $dispatcher;
106
+    }
107
+
108
+    /**
109
+     * @return string[] $appId => $enabled
110
+     */
111
+    private function getInstalledAppsValues() {
112
+        if (!$this->installedAppsCache) {
113
+            $values = $this->appConfig->getValues(false, 'enabled');
114
+
115
+            $alwaysEnabledApps = $this->getAlwaysEnabledApps();
116
+            foreach($alwaysEnabledApps as $appId) {
117
+                $values[$appId] = 'yes';
118
+            }
119
+
120
+            $this->installedAppsCache = array_filter($values, function ($value) {
121
+                return $value !== 'no';
122
+            });
123
+            ksort($this->installedAppsCache);
124
+        }
125
+        return $this->installedAppsCache;
126
+    }
127
+
128
+    /**
129
+     * List all installed apps
130
+     *
131
+     * @return string[]
132
+     */
133
+    public function getInstalledApps() {
134
+        return array_keys($this->getInstalledAppsValues());
135
+    }
136
+
137
+    /**
138
+     * List all apps enabled for a user
139
+     *
140
+     * @param \OCP\IUser $user
141
+     * @return string[]
142
+     */
143
+    public function getEnabledAppsForUser(IUser $user) {
144
+        $apps = $this->getInstalledAppsValues();
145
+        $appsForUser = array_filter($apps, function ($enabled) use ($user) {
146
+            return $this->checkAppForUser($enabled, $user);
147
+        });
148
+        return array_keys($appsForUser);
149
+    }
150
+
151
+    /**
152
+     * Check if an app is enabled for user
153
+     *
154
+     * @param string $appId
155
+     * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
156
+     * @return bool
157
+     */
158
+    public function isEnabledForUser($appId, $user = null) {
159
+        if ($this->isAlwaysEnabled($appId)) {
160
+            return true;
161
+        }
162
+        if ($user === null) {
163
+            $user = $this->userSession->getUser();
164
+        }
165
+        $installedApps = $this->getInstalledAppsValues();
166
+        if (isset($installedApps[$appId])) {
167
+            return $this->checkAppForUser($installedApps[$appId], $user);
168
+        } else {
169
+            return false;
170
+        }
171
+    }
172
+
173
+    /**
174
+     * @param string $enabled
175
+     * @param IUser $user
176
+     * @return bool
177
+     */
178
+    private function checkAppForUser($enabled, $user) {
179
+        if ($enabled === 'yes') {
180
+            return true;
181
+        } elseif ($user === null) {
182
+            return false;
183
+        } else {
184
+            if(empty($enabled)){
185
+                return false;
186
+            }
187
+
188
+            $groupIds = json_decode($enabled);
189
+
190
+            if (!is_array($groupIds)) {
191
+                $jsonError = json_last_error();
192
+                \OC::$server->getLogger()->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
193
+                return false;
194
+            }
195
+
196
+            $userGroups = $this->groupManager->getUserGroupIds($user);
197
+            foreach ($userGroups as $groupId) {
198
+                if (in_array($groupId, $groupIds, true)) {
199
+                    return true;
200
+                }
201
+            }
202
+            return false;
203
+        }
204
+    }
205
+
206
+    /**
207
+     * Check if an app is enabled in the instance
208
+     *
209
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
210
+     *
211
+     * @param string $appId
212
+     * @return bool
213
+     */
214
+    public function isInstalled($appId) {
215
+        $installedApps = $this->getInstalledAppsValues();
216
+        return isset($installedApps[$appId]);
217
+    }
218
+
219
+    /**
220
+     * Enable an app for every user
221
+     *
222
+     * @param string $appId
223
+     * @throws AppPathNotFoundException
224
+     */
225
+    public function enableApp($appId) {
226
+        // Check if app exists
227
+        $this->getAppPath($appId);
228
+
229
+        $this->installedAppsCache[$appId] = 'yes';
230
+        $this->appConfig->setValue($appId, 'enabled', 'yes');
231
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
232
+            ManagerEvent::EVENT_APP_ENABLE, $appId
233
+        ));
234
+        $this->clearAppsCache();
235
+    }
236
+
237
+    /**
238
+     * Whether a list of types contains a protected app type
239
+     *
240
+     * @param string[] $types
241
+     * @return bool
242
+     */
243
+    public function hasProtectedAppType($types) {
244
+        if (empty($types)) {
245
+            return false;
246
+        }
247
+
248
+        $protectedTypes = array_intersect($this->protectedAppTypes, $types);
249
+        return !empty($protectedTypes);
250
+    }
251
+
252
+    /**
253
+     * Enable an app only for specific groups
254
+     *
255
+     * @param string $appId
256
+     * @param \OCP\IGroup[] $groups
257
+     * @throws \Exception if app can't be enabled for groups
258
+     */
259
+    public function enableAppForGroups($appId, $groups) {
260
+        $info = $this->getAppInfo($appId);
261
+        if (!empty($info['types'])) {
262
+            $protectedTypes = array_intersect($this->protectedAppTypes, $info['types']);
263
+            if (!empty($protectedTypes)) {
264
+                throw new \Exception("$appId can't be enabled for groups.");
265
+            }
266
+        }
267
+
268
+        $groupIds = array_map(function ($group) {
269
+            /** @var \OCP\IGroup $group */
270
+            return $group->getGID();
271
+        }, $groups);
272
+        $this->installedAppsCache[$appId] = json_encode($groupIds);
273
+        $this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
274
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
275
+            ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
276
+        ));
277
+        $this->clearAppsCache();
278
+    }
279
+
280
+    /**
281
+     * Disable an app for every user
282
+     *
283
+     * @param string $appId
284
+     * @throws \Exception if app can't be disabled
285
+     */
286
+    public function disableApp($appId) {
287
+        if ($this->isAlwaysEnabled($appId)) {
288
+            throw new \Exception("$appId can't be disabled.");
289
+        }
290
+        unset($this->installedAppsCache[$appId]);
291
+        $this->appConfig->setValue($appId, 'enabled', 'no');
292
+
293
+        // run uninstall steps
294
+        $appData = $this->getAppInfo($appId);
295
+        if (!is_null($appData)) {
296
+            \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
297
+        }
298
+
299
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
300
+            ManagerEvent::EVENT_APP_DISABLE, $appId
301
+        ));
302
+        $this->clearAppsCache();
303
+    }
304
+
305
+    /**
306
+     * Get the directory for the given app.
307
+     *
308
+     * @param string $appId
309
+     * @return string
310
+     * @throws AppPathNotFoundException if app folder can't be found
311
+     */
312
+    public function getAppPath($appId) {
313
+        $appPath = \OC_App::getAppPath($appId);
314
+        if($appPath === false) {
315
+            throw new AppPathNotFoundException('Could not find path for ' . $appId);
316
+        }
317
+        return $appPath;
318
+    }
319
+
320
+    /**
321
+     * Clear the cached list of apps when enabling/disabling an app
322
+     */
323
+    public function clearAppsCache() {
324
+        $settingsMemCache = $this->memCacheFactory->createDistributed('settings');
325
+        $settingsMemCache->clear('listApps');
326
+        $this->appInfos = [];
327
+    }
328
+
329
+    /**
330
+     * Returns a list of apps that need upgrade
331
+     *
332
+     * @param string $version Nextcloud version as array of version components
333
+     * @return array list of app info from apps that need an upgrade
334
+     *
335
+     * @internal
336
+     */
337
+    public function getAppsNeedingUpgrade($version) {
338
+        $appsToUpgrade = [];
339
+        $apps = $this->getInstalledApps();
340
+        foreach ($apps as $appId) {
341
+            $appInfo = $this->getAppInfo($appId);
342
+            $appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
343
+            if ($appDbVersion
344
+                && isset($appInfo['version'])
345
+                && version_compare($appInfo['version'], $appDbVersion, '>')
346
+                && \OC_App::isAppCompatible($version, $appInfo)
347
+            ) {
348
+                $appsToUpgrade[] = $appInfo;
349
+            }
350
+        }
351
+
352
+        return $appsToUpgrade;
353
+    }
354
+
355
+    /**
356
+     * Returns the app information from "appinfo/info.xml".
357
+     *
358
+     * @param string $appId app id
359
+     *
360
+     * @param bool $path
361
+     * @param null $lang
362
+     * @return array|null app info
363
+     */
364
+    public function getAppInfo(string $appId, bool $path = false, $lang = null) {
365
+        if ($path) {
366
+            $file = $appId;
367
+        } else {
368
+            if ($lang === null && isset($this->appInfos[$appId])) {
369
+                return $this->appInfos[$appId];
370
+            }
371
+            try {
372
+                $appPath = $this->getAppPath($appId);
373
+            } catch (AppPathNotFoundException $e) {
374
+                return null;
375
+            }
376
+            $file = $appPath . '/appinfo/info.xml';
377
+        }
378
+
379
+        $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
380
+        $data = $parser->parse($file);
381
+
382
+        if (is_array($data)) {
383
+            $data = \OC_App::parseAppInfo($data, $lang);
384
+        }
385
+
386
+        if ($lang === null) {
387
+            $this->appInfos[$appId] = $data;
388
+        }
389
+
390
+        return $data;
391
+    }
392
+
393
+    public function getAppVersion(string $appId, bool $useCache = true): string {
394
+        if(!$useCache || !isset($this->appVersions[$appId])) {
395
+            $appInfo = \OC::$server->getAppManager()->getAppInfo($appId);
396
+            $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
397
+        }
398
+        return $this->appVersions[$appId];
399
+    }
400
+
401
+    /**
402
+     * Returns a list of apps incompatible with the given version
403
+     *
404
+     * @param string $version Nextcloud version as array of version components
405
+     *
406
+     * @return array list of app info from incompatible apps
407
+     *
408
+     * @internal
409
+     */
410
+    public function getIncompatibleApps(string $version): array {
411
+        $apps = $this->getInstalledApps();
412
+        $incompatibleApps = array();
413
+        foreach ($apps as $appId) {
414
+            $info = $this->getAppInfo($appId);
415
+            if ($info === null) {
416
+                $incompatibleApps[] = ['id' => $appId];
417
+            } else if (!\OC_App::isAppCompatible($version, $info)) {
418
+                $incompatibleApps[] = $info;
419
+            }
420
+        }
421
+        return $incompatibleApps;
422
+    }
423
+
424
+    /**
425
+     * @inheritdoc
426
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
427
+     */
428
+    public function isShipped($appId) {
429
+        $this->loadShippedJson();
430
+        return in_array($appId, $this->shippedApps, true);
431
+    }
432
+
433
+    private function isAlwaysEnabled($appId) {
434
+        $alwaysEnabled = $this->getAlwaysEnabledApps();
435
+        return in_array($appId, $alwaysEnabled, true);
436
+    }
437
+
438
+    /**
439
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
440
+     * @throws \Exception
441
+     */
442
+    private function loadShippedJson() {
443
+        if ($this->shippedApps === null) {
444
+            $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
445
+            if (!file_exists($shippedJson)) {
446
+                throw new \Exception("File not found: $shippedJson");
447
+            }
448
+            $content = json_decode(file_get_contents($shippedJson), true);
449
+            $this->shippedApps = $content['shippedApps'];
450
+            $this->alwaysEnabled = $content['alwaysEnabled'];
451
+        }
452
+    }
453
+
454
+    /**
455
+     * @inheritdoc
456
+     */
457
+    public function getAlwaysEnabledApps() {
458
+        $this->loadShippedJson();
459
+        return $this->alwaysEnabled;
460
+    }
461 461
 }
Please login to merge, or discard this patch.