Passed
Push — master ( 4c9324...c04e0d )
by John
34:08 queued 19:27
created
lib/private/App/AppManager.php 1 patch
Indentation   +528 added lines, -528 removed lines patch added patch discarded remove patch
@@ -52,532 +52,532 @@
 block discarded – undo
52 52
 
53 53
 class AppManager implements IAppManager {
54 54
 
55
-	/**
56
-	 * Apps with these types can not be enabled for certain groups only
57
-	 * @var string[]
58
-	 */
59
-	protected $protectedAppTypes = [
60
-		'filesystem',
61
-		'prelogin',
62
-		'authentication',
63
-		'logging',
64
-		'prevent_group_restriction',
65
-	];
66
-
67
-	/** @var IUserSession */
68
-	private $userSession;
69
-
70
-	/** @var IConfig */
71
-	private $config;
72
-
73
-	/** @var AppConfig */
74
-	private $appConfig;
75
-
76
-	/** @var IGroupManager */
77
-	private $groupManager;
78
-
79
-	/** @var ICacheFactory */
80
-	private $memCacheFactory;
81
-
82
-	/** @var EventDispatcherInterface */
83
-	private $dispatcher;
84
-
85
-	/** @var LoggerInterface */
86
-	private $logger;
87
-
88
-	/** @var string[] $appId => $enabled */
89
-	private $installedAppsCache;
90
-
91
-	/** @var string[] */
92
-	private $shippedApps;
93
-
94
-	/** @var string[] */
95
-	private $alwaysEnabled;
96
-
97
-	/** @var array */
98
-	private $appInfos = [];
99
-
100
-	/** @var array */
101
-	private $appVersions = [];
102
-
103
-	/** @var array */
104
-	private $autoDisabledApps = [];
105
-
106
-	public function __construct(IUserSession $userSession,
107
-								IConfig $config,
108
-								AppConfig $appConfig,
109
-								IGroupManager $groupManager,
110
-								ICacheFactory $memCacheFactory,
111
-								EventDispatcherInterface $dispatcher,
112
-								LoggerInterface $logger) {
113
-		$this->userSession = $userSession;
114
-		$this->config = $config;
115
-		$this->appConfig = $appConfig;
116
-		$this->groupManager = $groupManager;
117
-		$this->memCacheFactory = $memCacheFactory;
118
-		$this->dispatcher = $dispatcher;
119
-		$this->logger = $logger;
120
-	}
121
-
122
-	/**
123
-	 * @return string[] $appId => $enabled
124
-	 */
125
-	private function getInstalledAppsValues() {
126
-		if (!$this->installedAppsCache) {
127
-			$values = $this->appConfig->getValues(false, 'enabled');
128
-
129
-			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
130
-			foreach ($alwaysEnabledApps as $appId) {
131
-				$values[$appId] = 'yes';
132
-			}
133
-
134
-			$this->installedAppsCache = array_filter($values, function ($value) {
135
-				return $value !== 'no';
136
-			});
137
-			ksort($this->installedAppsCache);
138
-		}
139
-		return $this->installedAppsCache;
140
-	}
141
-
142
-	/**
143
-	 * List all installed apps
144
-	 *
145
-	 * @return string[]
146
-	 */
147
-	public function getInstalledApps() {
148
-		return array_keys($this->getInstalledAppsValues());
149
-	}
150
-
151
-	/**
152
-	 * List all apps enabled for a user
153
-	 *
154
-	 * @param \OCP\IUser $user
155
-	 * @return string[]
156
-	 */
157
-	public function getEnabledAppsForUser(IUser $user) {
158
-		$apps = $this->getInstalledAppsValues();
159
-		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
160
-			return $this->checkAppForUser($enabled, $user);
161
-		});
162
-		return array_keys($appsForUser);
163
-	}
164
-
165
-	/**
166
-	 * @param \OCP\IGroup $group
167
-	 * @return array
168
-	 */
169
-	public function getEnabledAppsForGroup(IGroup $group): array {
170
-		$apps = $this->getInstalledAppsValues();
171
-		$appsForGroups = array_filter($apps, function ($enabled) use ($group) {
172
-			return $this->checkAppForGroups($enabled, $group);
173
-		});
174
-		return array_keys($appsForGroups);
175
-	}
176
-
177
-	/**
178
-	 * @return array
179
-	 */
180
-	public function getAutoDisabledApps(): array {
181
-		return $this->autoDisabledApps;
182
-	}
183
-
184
-	/**
185
-	 * @param string $appId
186
-	 * @return array
187
-	 */
188
-	public function getAppRestriction(string $appId): array {
189
-		$values = $this->getInstalledAppsValues();
190
-
191
-		if (!isset($values[$appId])) {
192
-			return [];
193
-		}
194
-
195
-		if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
196
-			return [];
197
-		}
198
-		return json_decode($values[$appId]);
199
-	}
200
-
201
-
202
-	/**
203
-	 * Check if an app is enabled for user
204
-	 *
205
-	 * @param string $appId
206
-	 * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
207
-	 * @return bool
208
-	 */
209
-	public function isEnabledForUser($appId, $user = null) {
210
-		if ($this->isAlwaysEnabled($appId)) {
211
-			return true;
212
-		}
213
-		if ($user === null) {
214
-			$user = $this->userSession->getUser();
215
-		}
216
-		$installedApps = $this->getInstalledAppsValues();
217
-		if (isset($installedApps[$appId])) {
218
-			return $this->checkAppForUser($installedApps[$appId], $user);
219
-		} else {
220
-			return false;
221
-		}
222
-	}
223
-
224
-	/**
225
-	 * @param string $enabled
226
-	 * @param IUser $user
227
-	 * @return bool
228
-	 */
229
-	private function checkAppForUser($enabled, $user) {
230
-		if ($enabled === 'yes') {
231
-			return true;
232
-		} elseif ($user === null) {
233
-			return false;
234
-		} else {
235
-			if (empty($enabled)) {
236
-				return false;
237
-			}
238
-
239
-			$groupIds = json_decode($enabled);
240
-
241
-			if (!is_array($groupIds)) {
242
-				$jsonError = json_last_error();
243
-				$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
244
-				return false;
245
-			}
246
-
247
-			$userGroups = $this->groupManager->getUserGroupIds($user);
248
-			foreach ($userGroups as $groupId) {
249
-				if (in_array($groupId, $groupIds, true)) {
250
-					return true;
251
-				}
252
-			}
253
-			return false;
254
-		}
255
-	}
256
-
257
-	/**
258
-	 * @param string $enabled
259
-	 * @param IGroup $group
260
-	 * @return bool
261
-	 */
262
-	private function checkAppForGroups(string $enabled, IGroup $group): bool {
263
-		if ($enabled === 'yes') {
264
-			return true;
265
-		} elseif ($group === null) {
266
-			return false;
267
-		} else {
268
-			if (empty($enabled)) {
269
-				return false;
270
-			}
271
-
272
-			$groupIds = json_decode($enabled);
273
-
274
-			if (!is_array($groupIds)) {
275
-				$jsonError = json_last_error();
276
-				$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
277
-				return false;
278
-			}
279
-
280
-			return in_array($group->getGID(), $groupIds);
281
-		}
282
-	}
283
-
284
-	/**
285
-	 * Check if an app is enabled in the instance
286
-	 *
287
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
288
-	 *
289
-	 * @param string $appId
290
-	 * @param \OCP\IGroup[]|String[] $groups
291
-	 * @return bool
292
-	 */
293
-	public function isInstalled($appId) {
294
-		$installedApps = $this->getInstalledAppsValues();
295
-		return isset($installedApps[$appId]);
296
-	}
297
-
298
-	public function ignoreNextcloudRequirementForApp(string $appId): void {
299
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
300
-		if (!in_array($appId, $ignoreMaxApps, true)) {
301
-			$ignoreMaxApps[] = $appId;
302
-			$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
303
-		}
304
-	}
305
-
306
-	/**
307
-	 * Enable an app for every user
308
-	 *
309
-	 * @param string $appId
310
-	 * @param bool $forceEnable
311
-	 * @throws AppPathNotFoundException
312
-	 */
313
-	public function enableApp(string $appId, bool $forceEnable = false): void {
314
-		// Check if app exists
315
-		$this->getAppPath($appId);
316
-
317
-		if ($forceEnable) {
318
-			$this->ignoreNextcloudRequirementForApp($appId);
319
-		}
320
-
321
-		$this->installedAppsCache[$appId] = 'yes';
322
-		$this->appConfig->setValue($appId, 'enabled', 'yes');
323
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
324
-			ManagerEvent::EVENT_APP_ENABLE, $appId
325
-		));
326
-		$this->clearAppsCache();
327
-	}
328
-
329
-	/**
330
-	 * Whether a list of types contains a protected app type
331
-	 *
332
-	 * @param string[] $types
333
-	 * @return bool
334
-	 */
335
-	public function hasProtectedAppType($types) {
336
-		if (empty($types)) {
337
-			return false;
338
-		}
339
-
340
-		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
341
-		return !empty($protectedTypes);
342
-	}
343
-
344
-	/**
345
-	 * Enable an app only for specific groups
346
-	 *
347
-	 * @param string $appId
348
-	 * @param \OCP\IGroup[] $groups
349
-	 * @param bool $forceEnable
350
-	 * @throws \InvalidArgumentException if app can't be enabled for groups
351
-	 * @throws AppPathNotFoundException
352
-	 */
353
-	public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
354
-		// Check if app exists
355
-		$this->getAppPath($appId);
356
-
357
-		$info = $this->getAppInfo($appId);
358
-		if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
359
-			throw new \InvalidArgumentException("$appId can't be enabled for groups.");
360
-		}
361
-
362
-		if ($forceEnable) {
363
-			$this->ignoreNextcloudRequirementForApp($appId);
364
-		}
365
-
366
-		$groupIds = array_map(function ($group) {
367
-			/** @var \OCP\IGroup $group */
368
-			return ($group instanceof IGroup)
369
-				? $group->getGID()
370
-				: $group;
371
-		}, $groups);
372
-
373
-		$this->installedAppsCache[$appId] = json_encode($groupIds);
374
-		$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
375
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
376
-			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
377
-		));
378
-		$this->clearAppsCache();
379
-	}
380
-
381
-	/**
382
-	 * Disable an app for every user
383
-	 *
384
-	 * @param string $appId
385
-	 * @param bool $automaticDisabled
386
-	 * @throws \Exception if app can't be disabled
387
-	 */
388
-	public function disableApp($appId, $automaticDisabled = false) {
389
-		if ($this->isAlwaysEnabled($appId)) {
390
-			throw new \Exception("$appId can't be disabled.");
391
-		}
392
-
393
-		if ($automaticDisabled) {
394
-			$this->autoDisabledApps[] = $appId;
395
-		}
396
-
397
-		unset($this->installedAppsCache[$appId]);
398
-		$this->appConfig->setValue($appId, 'enabled', 'no');
399
-
400
-		// run uninstall steps
401
-		$appData = $this->getAppInfo($appId);
402
-		if (!is_null($appData)) {
403
-			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
404
-		}
405
-
406
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
407
-			ManagerEvent::EVENT_APP_DISABLE, $appId
408
-		));
409
-		$this->clearAppsCache();
410
-	}
411
-
412
-	/**
413
-	 * Get the directory for the given app.
414
-	 *
415
-	 * @param string $appId
416
-	 * @return string
417
-	 * @throws AppPathNotFoundException if app folder can't be found
418
-	 */
419
-	public function getAppPath($appId) {
420
-		$appPath = \OC_App::getAppPath($appId);
421
-		if ($appPath === false) {
422
-			throw new AppPathNotFoundException('Could not find path for ' . $appId);
423
-		}
424
-		return $appPath;
425
-	}
426
-
427
-	/**
428
-	 * Get the web path for the given app.
429
-	 *
430
-	 * @param string $appId
431
-	 * @return string
432
-	 * @throws AppPathNotFoundException if app path can't be found
433
-	 */
434
-	public function getAppWebPath(string $appId): string {
435
-		$appWebPath = \OC_App::getAppWebPath($appId);
436
-		if ($appWebPath === false) {
437
-			throw new AppPathNotFoundException('Could not find web path for ' . $appId);
438
-		}
439
-		return $appWebPath;
440
-	}
441
-
442
-	/**
443
-	 * Clear the cached list of apps when enabling/disabling an app
444
-	 */
445
-	public function clearAppsCache() {
446
-		$settingsMemCache = $this->memCacheFactory->createDistributed('settings');
447
-		$settingsMemCache->clear('listApps');
448
-		$this->appInfos = [];
449
-	}
450
-
451
-	/**
452
-	 * Returns a list of apps that need upgrade
453
-	 *
454
-	 * @param string $version Nextcloud version as array of version components
455
-	 * @return array list of app info from apps that need an upgrade
456
-	 *
457
-	 * @internal
458
-	 */
459
-	public function getAppsNeedingUpgrade($version) {
460
-		$appsToUpgrade = [];
461
-		$apps = $this->getInstalledApps();
462
-		foreach ($apps as $appId) {
463
-			$appInfo = $this->getAppInfo($appId);
464
-			$appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
465
-			if ($appDbVersion
466
-				&& isset($appInfo['version'])
467
-				&& version_compare($appInfo['version'], $appDbVersion, '>')
468
-				&& \OC_App::isAppCompatible($version, $appInfo)
469
-			) {
470
-				$appsToUpgrade[] = $appInfo;
471
-			}
472
-		}
473
-
474
-		return $appsToUpgrade;
475
-	}
476
-
477
-	/**
478
-	 * Returns the app information from "appinfo/info.xml".
479
-	 *
480
-	 * @param string $appId app id
481
-	 *
482
-	 * @param bool $path
483
-	 * @param null $lang
484
-	 * @return array|null app info
485
-	 */
486
-	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
487
-		if ($path) {
488
-			$file = $appId;
489
-		} else {
490
-			if ($lang === null && isset($this->appInfos[$appId])) {
491
-				return $this->appInfos[$appId];
492
-			}
493
-			try {
494
-				$appPath = $this->getAppPath($appId);
495
-			} catch (AppPathNotFoundException $e) {
496
-				return null;
497
-			}
498
-			$file = $appPath . '/appinfo/info.xml';
499
-		}
500
-
501
-		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
502
-		$data = $parser->parse($file);
503
-
504
-		if (is_array($data)) {
505
-			$data = \OC_App::parseAppInfo($data, $lang);
506
-		}
507
-
508
-		if ($lang === null) {
509
-			$this->appInfos[$appId] = $data;
510
-		}
511
-
512
-		return $data;
513
-	}
514
-
515
-	public function getAppVersion(string $appId, bool $useCache = true): string {
516
-		if (!$useCache || !isset($this->appVersions[$appId])) {
517
-			$appInfo = $this->getAppInfo($appId);
518
-			$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
519
-		}
520
-		return $this->appVersions[$appId];
521
-	}
522
-
523
-	/**
524
-	 * Returns a list of apps incompatible with the given version
525
-	 *
526
-	 * @param string $version Nextcloud version as array of version components
527
-	 *
528
-	 * @return array list of app info from incompatible apps
529
-	 *
530
-	 * @internal
531
-	 */
532
-	public function getIncompatibleApps(string $version): array {
533
-		$apps = $this->getInstalledApps();
534
-		$incompatibleApps = [];
535
-		foreach ($apps as $appId) {
536
-			$info = $this->getAppInfo($appId);
537
-			if ($info === null) {
538
-				$incompatibleApps[] = ['id' => $appId, 'name' => $appId];
539
-			} elseif (!\OC_App::isAppCompatible($version, $info)) {
540
-				$incompatibleApps[] = $info;
541
-			}
542
-		}
543
-		return $incompatibleApps;
544
-	}
545
-
546
-	/**
547
-	 * @inheritdoc
548
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
549
-	 */
550
-	public function isShipped($appId) {
551
-		$this->loadShippedJson();
552
-		return in_array($appId, $this->shippedApps, true);
553
-	}
554
-
555
-	private function isAlwaysEnabled($appId) {
556
-		$alwaysEnabled = $this->getAlwaysEnabledApps();
557
-		return in_array($appId, $alwaysEnabled, true);
558
-	}
559
-
560
-	/**
561
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
562
-	 * @throws \Exception
563
-	 */
564
-	private function loadShippedJson() {
565
-		if ($this->shippedApps === null) {
566
-			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
567
-			if (!file_exists($shippedJson)) {
568
-				throw new \Exception("File not found: $shippedJson");
569
-			}
570
-			$content = json_decode(file_get_contents($shippedJson), true);
571
-			$this->shippedApps = $content['shippedApps'];
572
-			$this->alwaysEnabled = $content['alwaysEnabled'];
573
-		}
574
-	}
575
-
576
-	/**
577
-	 * @inheritdoc
578
-	 */
579
-	public function getAlwaysEnabledApps() {
580
-		$this->loadShippedJson();
581
-		return $this->alwaysEnabled;
582
-	}
55
+    /**
56
+     * Apps with these types can not be enabled for certain groups only
57
+     * @var string[]
58
+     */
59
+    protected $protectedAppTypes = [
60
+        'filesystem',
61
+        'prelogin',
62
+        'authentication',
63
+        'logging',
64
+        'prevent_group_restriction',
65
+    ];
66
+
67
+    /** @var IUserSession */
68
+    private $userSession;
69
+
70
+    /** @var IConfig */
71
+    private $config;
72
+
73
+    /** @var AppConfig */
74
+    private $appConfig;
75
+
76
+    /** @var IGroupManager */
77
+    private $groupManager;
78
+
79
+    /** @var ICacheFactory */
80
+    private $memCacheFactory;
81
+
82
+    /** @var EventDispatcherInterface */
83
+    private $dispatcher;
84
+
85
+    /** @var LoggerInterface */
86
+    private $logger;
87
+
88
+    /** @var string[] $appId => $enabled */
89
+    private $installedAppsCache;
90
+
91
+    /** @var string[] */
92
+    private $shippedApps;
93
+
94
+    /** @var string[] */
95
+    private $alwaysEnabled;
96
+
97
+    /** @var array */
98
+    private $appInfos = [];
99
+
100
+    /** @var array */
101
+    private $appVersions = [];
102
+
103
+    /** @var array */
104
+    private $autoDisabledApps = [];
105
+
106
+    public function __construct(IUserSession $userSession,
107
+                                IConfig $config,
108
+                                AppConfig $appConfig,
109
+                                IGroupManager $groupManager,
110
+                                ICacheFactory $memCacheFactory,
111
+                                EventDispatcherInterface $dispatcher,
112
+                                LoggerInterface $logger) {
113
+        $this->userSession = $userSession;
114
+        $this->config = $config;
115
+        $this->appConfig = $appConfig;
116
+        $this->groupManager = $groupManager;
117
+        $this->memCacheFactory = $memCacheFactory;
118
+        $this->dispatcher = $dispatcher;
119
+        $this->logger = $logger;
120
+    }
121
+
122
+    /**
123
+     * @return string[] $appId => $enabled
124
+     */
125
+    private function getInstalledAppsValues() {
126
+        if (!$this->installedAppsCache) {
127
+            $values = $this->appConfig->getValues(false, 'enabled');
128
+
129
+            $alwaysEnabledApps = $this->getAlwaysEnabledApps();
130
+            foreach ($alwaysEnabledApps as $appId) {
131
+                $values[$appId] = 'yes';
132
+            }
133
+
134
+            $this->installedAppsCache = array_filter($values, function ($value) {
135
+                return $value !== 'no';
136
+            });
137
+            ksort($this->installedAppsCache);
138
+        }
139
+        return $this->installedAppsCache;
140
+    }
141
+
142
+    /**
143
+     * List all installed apps
144
+     *
145
+     * @return string[]
146
+     */
147
+    public function getInstalledApps() {
148
+        return array_keys($this->getInstalledAppsValues());
149
+    }
150
+
151
+    /**
152
+     * List all apps enabled for a user
153
+     *
154
+     * @param \OCP\IUser $user
155
+     * @return string[]
156
+     */
157
+    public function getEnabledAppsForUser(IUser $user) {
158
+        $apps = $this->getInstalledAppsValues();
159
+        $appsForUser = array_filter($apps, function ($enabled) use ($user) {
160
+            return $this->checkAppForUser($enabled, $user);
161
+        });
162
+        return array_keys($appsForUser);
163
+    }
164
+
165
+    /**
166
+     * @param \OCP\IGroup $group
167
+     * @return array
168
+     */
169
+    public function getEnabledAppsForGroup(IGroup $group): array {
170
+        $apps = $this->getInstalledAppsValues();
171
+        $appsForGroups = array_filter($apps, function ($enabled) use ($group) {
172
+            return $this->checkAppForGroups($enabled, $group);
173
+        });
174
+        return array_keys($appsForGroups);
175
+    }
176
+
177
+    /**
178
+     * @return array
179
+     */
180
+    public function getAutoDisabledApps(): array {
181
+        return $this->autoDisabledApps;
182
+    }
183
+
184
+    /**
185
+     * @param string $appId
186
+     * @return array
187
+     */
188
+    public function getAppRestriction(string $appId): array {
189
+        $values = $this->getInstalledAppsValues();
190
+
191
+        if (!isset($values[$appId])) {
192
+            return [];
193
+        }
194
+
195
+        if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
196
+            return [];
197
+        }
198
+        return json_decode($values[$appId]);
199
+    }
200
+
201
+
202
+    /**
203
+     * Check if an app is enabled for user
204
+     *
205
+     * @param string $appId
206
+     * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
207
+     * @return bool
208
+     */
209
+    public function isEnabledForUser($appId, $user = null) {
210
+        if ($this->isAlwaysEnabled($appId)) {
211
+            return true;
212
+        }
213
+        if ($user === null) {
214
+            $user = $this->userSession->getUser();
215
+        }
216
+        $installedApps = $this->getInstalledAppsValues();
217
+        if (isset($installedApps[$appId])) {
218
+            return $this->checkAppForUser($installedApps[$appId], $user);
219
+        } else {
220
+            return false;
221
+        }
222
+    }
223
+
224
+    /**
225
+     * @param string $enabled
226
+     * @param IUser $user
227
+     * @return bool
228
+     */
229
+    private function checkAppForUser($enabled, $user) {
230
+        if ($enabled === 'yes') {
231
+            return true;
232
+        } elseif ($user === null) {
233
+            return false;
234
+        } else {
235
+            if (empty($enabled)) {
236
+                return false;
237
+            }
238
+
239
+            $groupIds = json_decode($enabled);
240
+
241
+            if (!is_array($groupIds)) {
242
+                $jsonError = json_last_error();
243
+                $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
244
+                return false;
245
+            }
246
+
247
+            $userGroups = $this->groupManager->getUserGroupIds($user);
248
+            foreach ($userGroups as $groupId) {
249
+                if (in_array($groupId, $groupIds, true)) {
250
+                    return true;
251
+                }
252
+            }
253
+            return false;
254
+        }
255
+    }
256
+
257
+    /**
258
+     * @param string $enabled
259
+     * @param IGroup $group
260
+     * @return bool
261
+     */
262
+    private function checkAppForGroups(string $enabled, IGroup $group): bool {
263
+        if ($enabled === 'yes') {
264
+            return true;
265
+        } elseif ($group === null) {
266
+            return false;
267
+        } else {
268
+            if (empty($enabled)) {
269
+                return false;
270
+            }
271
+
272
+            $groupIds = json_decode($enabled);
273
+
274
+            if (!is_array($groupIds)) {
275
+                $jsonError = json_last_error();
276
+                $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
277
+                return false;
278
+            }
279
+
280
+            return in_array($group->getGID(), $groupIds);
281
+        }
282
+    }
283
+
284
+    /**
285
+     * Check if an app is enabled in the instance
286
+     *
287
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
288
+     *
289
+     * @param string $appId
290
+     * @param \OCP\IGroup[]|String[] $groups
291
+     * @return bool
292
+     */
293
+    public function isInstalled($appId) {
294
+        $installedApps = $this->getInstalledAppsValues();
295
+        return isset($installedApps[$appId]);
296
+    }
297
+
298
+    public function ignoreNextcloudRequirementForApp(string $appId): void {
299
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
300
+        if (!in_array($appId, $ignoreMaxApps, true)) {
301
+            $ignoreMaxApps[] = $appId;
302
+            $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
303
+        }
304
+    }
305
+
306
+    /**
307
+     * Enable an app for every user
308
+     *
309
+     * @param string $appId
310
+     * @param bool $forceEnable
311
+     * @throws AppPathNotFoundException
312
+     */
313
+    public function enableApp(string $appId, bool $forceEnable = false): void {
314
+        // Check if app exists
315
+        $this->getAppPath($appId);
316
+
317
+        if ($forceEnable) {
318
+            $this->ignoreNextcloudRequirementForApp($appId);
319
+        }
320
+
321
+        $this->installedAppsCache[$appId] = 'yes';
322
+        $this->appConfig->setValue($appId, 'enabled', 'yes');
323
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
324
+            ManagerEvent::EVENT_APP_ENABLE, $appId
325
+        ));
326
+        $this->clearAppsCache();
327
+    }
328
+
329
+    /**
330
+     * Whether a list of types contains a protected app type
331
+     *
332
+     * @param string[] $types
333
+     * @return bool
334
+     */
335
+    public function hasProtectedAppType($types) {
336
+        if (empty($types)) {
337
+            return false;
338
+        }
339
+
340
+        $protectedTypes = array_intersect($this->protectedAppTypes, $types);
341
+        return !empty($protectedTypes);
342
+    }
343
+
344
+    /**
345
+     * Enable an app only for specific groups
346
+     *
347
+     * @param string $appId
348
+     * @param \OCP\IGroup[] $groups
349
+     * @param bool $forceEnable
350
+     * @throws \InvalidArgumentException if app can't be enabled for groups
351
+     * @throws AppPathNotFoundException
352
+     */
353
+    public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
354
+        // Check if app exists
355
+        $this->getAppPath($appId);
356
+
357
+        $info = $this->getAppInfo($appId);
358
+        if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
359
+            throw new \InvalidArgumentException("$appId can't be enabled for groups.");
360
+        }
361
+
362
+        if ($forceEnable) {
363
+            $this->ignoreNextcloudRequirementForApp($appId);
364
+        }
365
+
366
+        $groupIds = array_map(function ($group) {
367
+            /** @var \OCP\IGroup $group */
368
+            return ($group instanceof IGroup)
369
+                ? $group->getGID()
370
+                : $group;
371
+        }, $groups);
372
+
373
+        $this->installedAppsCache[$appId] = json_encode($groupIds);
374
+        $this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
375
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
376
+            ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
377
+        ));
378
+        $this->clearAppsCache();
379
+    }
380
+
381
+    /**
382
+     * Disable an app for every user
383
+     *
384
+     * @param string $appId
385
+     * @param bool $automaticDisabled
386
+     * @throws \Exception if app can't be disabled
387
+     */
388
+    public function disableApp($appId, $automaticDisabled = false) {
389
+        if ($this->isAlwaysEnabled($appId)) {
390
+            throw new \Exception("$appId can't be disabled.");
391
+        }
392
+
393
+        if ($automaticDisabled) {
394
+            $this->autoDisabledApps[] = $appId;
395
+        }
396
+
397
+        unset($this->installedAppsCache[$appId]);
398
+        $this->appConfig->setValue($appId, 'enabled', 'no');
399
+
400
+        // run uninstall steps
401
+        $appData = $this->getAppInfo($appId);
402
+        if (!is_null($appData)) {
403
+            \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
404
+        }
405
+
406
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
407
+            ManagerEvent::EVENT_APP_DISABLE, $appId
408
+        ));
409
+        $this->clearAppsCache();
410
+    }
411
+
412
+    /**
413
+     * Get the directory for the given app.
414
+     *
415
+     * @param string $appId
416
+     * @return string
417
+     * @throws AppPathNotFoundException if app folder can't be found
418
+     */
419
+    public function getAppPath($appId) {
420
+        $appPath = \OC_App::getAppPath($appId);
421
+        if ($appPath === false) {
422
+            throw new AppPathNotFoundException('Could not find path for ' . $appId);
423
+        }
424
+        return $appPath;
425
+    }
426
+
427
+    /**
428
+     * Get the web path for the given app.
429
+     *
430
+     * @param string $appId
431
+     * @return string
432
+     * @throws AppPathNotFoundException if app path can't be found
433
+     */
434
+    public function getAppWebPath(string $appId): string {
435
+        $appWebPath = \OC_App::getAppWebPath($appId);
436
+        if ($appWebPath === false) {
437
+            throw new AppPathNotFoundException('Could not find web path for ' . $appId);
438
+        }
439
+        return $appWebPath;
440
+    }
441
+
442
+    /**
443
+     * Clear the cached list of apps when enabling/disabling an app
444
+     */
445
+    public function clearAppsCache() {
446
+        $settingsMemCache = $this->memCacheFactory->createDistributed('settings');
447
+        $settingsMemCache->clear('listApps');
448
+        $this->appInfos = [];
449
+    }
450
+
451
+    /**
452
+     * Returns a list of apps that need upgrade
453
+     *
454
+     * @param string $version Nextcloud version as array of version components
455
+     * @return array list of app info from apps that need an upgrade
456
+     *
457
+     * @internal
458
+     */
459
+    public function getAppsNeedingUpgrade($version) {
460
+        $appsToUpgrade = [];
461
+        $apps = $this->getInstalledApps();
462
+        foreach ($apps as $appId) {
463
+            $appInfo = $this->getAppInfo($appId);
464
+            $appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
465
+            if ($appDbVersion
466
+                && isset($appInfo['version'])
467
+                && version_compare($appInfo['version'], $appDbVersion, '>')
468
+                && \OC_App::isAppCompatible($version, $appInfo)
469
+            ) {
470
+                $appsToUpgrade[] = $appInfo;
471
+            }
472
+        }
473
+
474
+        return $appsToUpgrade;
475
+    }
476
+
477
+    /**
478
+     * Returns the app information from "appinfo/info.xml".
479
+     *
480
+     * @param string $appId app id
481
+     *
482
+     * @param bool $path
483
+     * @param null $lang
484
+     * @return array|null app info
485
+     */
486
+    public function getAppInfo(string $appId, bool $path = false, $lang = null) {
487
+        if ($path) {
488
+            $file = $appId;
489
+        } else {
490
+            if ($lang === null && isset($this->appInfos[$appId])) {
491
+                return $this->appInfos[$appId];
492
+            }
493
+            try {
494
+                $appPath = $this->getAppPath($appId);
495
+            } catch (AppPathNotFoundException $e) {
496
+                return null;
497
+            }
498
+            $file = $appPath . '/appinfo/info.xml';
499
+        }
500
+
501
+        $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
502
+        $data = $parser->parse($file);
503
+
504
+        if (is_array($data)) {
505
+            $data = \OC_App::parseAppInfo($data, $lang);
506
+        }
507
+
508
+        if ($lang === null) {
509
+            $this->appInfos[$appId] = $data;
510
+        }
511
+
512
+        return $data;
513
+    }
514
+
515
+    public function getAppVersion(string $appId, bool $useCache = true): string {
516
+        if (!$useCache || !isset($this->appVersions[$appId])) {
517
+            $appInfo = $this->getAppInfo($appId);
518
+            $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
519
+        }
520
+        return $this->appVersions[$appId];
521
+    }
522
+
523
+    /**
524
+     * Returns a list of apps incompatible with the given version
525
+     *
526
+     * @param string $version Nextcloud version as array of version components
527
+     *
528
+     * @return array list of app info from incompatible apps
529
+     *
530
+     * @internal
531
+     */
532
+    public function getIncompatibleApps(string $version): array {
533
+        $apps = $this->getInstalledApps();
534
+        $incompatibleApps = [];
535
+        foreach ($apps as $appId) {
536
+            $info = $this->getAppInfo($appId);
537
+            if ($info === null) {
538
+                $incompatibleApps[] = ['id' => $appId, 'name' => $appId];
539
+            } elseif (!\OC_App::isAppCompatible($version, $info)) {
540
+                $incompatibleApps[] = $info;
541
+            }
542
+        }
543
+        return $incompatibleApps;
544
+    }
545
+
546
+    /**
547
+     * @inheritdoc
548
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
549
+     */
550
+    public function isShipped($appId) {
551
+        $this->loadShippedJson();
552
+        return in_array($appId, $this->shippedApps, true);
553
+    }
554
+
555
+    private function isAlwaysEnabled($appId) {
556
+        $alwaysEnabled = $this->getAlwaysEnabledApps();
557
+        return in_array($appId, $alwaysEnabled, true);
558
+    }
559
+
560
+    /**
561
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
562
+     * @throws \Exception
563
+     */
564
+    private function loadShippedJson() {
565
+        if ($this->shippedApps === null) {
566
+            $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
567
+            if (!file_exists($shippedJson)) {
568
+                throw new \Exception("File not found: $shippedJson");
569
+            }
570
+            $content = json_decode(file_get_contents($shippedJson), true);
571
+            $this->shippedApps = $content['shippedApps'];
572
+            $this->alwaysEnabled = $content['alwaysEnabled'];
573
+        }
574
+    }
575
+
576
+    /**
577
+     * @inheritdoc
578
+     */
579
+    public function getAlwaysEnabledApps() {
580
+        $this->loadShippedJson();
581
+        return $this->alwaysEnabled;
582
+    }
583 583
 }
Please login to merge, or discard this patch.