Passed
Push — master ( 9fee1d...2df907 )
by Roeland
11:00 queued 10s
created

AppManager::getAppsNeedingUpgrade()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 3
nop 1
dl 0
loc 16
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Christoph Schaefer "christophł@wolkesicher.de"
8
 * @author Daniel Kesselberg <[email protected]>
9
 * @author Daniel Rudolf <[email protected]>
10
 * @author Greta Doci <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Julius Haertl <[email protected]>
13
 * @author Julius Härtl <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Thomas Müller <[email protected]>
19
 * @author Tobia De Koninck <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
38
namespace OC\App;
39
40
use OC\AppConfig;
41
use OCP\App\AppPathNotFoundException;
42
use OCP\App\IAppManager;
43
use OCP\App\ManagerEvent;
44
use OCP\ICacheFactory;
45
use OCP\IConfig;
46
use OCP\IGroup;
47
use OCP\IGroupManager;
48
use OCP\ILogger;
49
use OCP\IUser;
50
use OCP\IUserSession;
51
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
52
53
class AppManager implements IAppManager {
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 ILogger */
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
	/**
107
	 * @param IUserSession $userSession
108
	 * @param IConfig $config
109
	 * @param AppConfig $appConfig
110
	 * @param IGroupManager $groupManager
111
	 * @param ICacheFactory $memCacheFactory
112
	 * @param EventDispatcherInterface $dispatcher
113
	 */
114
	public function __construct(IUserSession $userSession,
115
								IConfig $config,
116
								AppConfig $appConfig,
117
								IGroupManager $groupManager,
118
								ICacheFactory $memCacheFactory,
119
								EventDispatcherInterface $dispatcher,
120
								ILogger $logger) {
121
		$this->userSession = $userSession;
122
		$this->config = $config;
123
		$this->appConfig = $appConfig;
124
		$this->groupManager = $groupManager;
125
		$this->memCacheFactory = $memCacheFactory;
126
		$this->dispatcher = $dispatcher;
127
		$this->logger = $logger;
128
	}
129
130
	/**
131
	 * @return string[] $appId => $enabled
132
	 */
133
	private function getInstalledAppsValues() {
134
		if (!$this->installedAppsCache) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->installedAppsCache of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
135
			$values = $this->appConfig->getValues(false, 'enabled');
136
137
			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
138
			foreach($alwaysEnabledApps as $appId) {
139
				$values[$appId] = 'yes';
140
			}
141
142
			$this->installedAppsCache = array_filter($values, function ($value) {
143
				return $value !== 'no';
144
			});
145
			ksort($this->installedAppsCache);
146
		}
147
		return $this->installedAppsCache;
148
	}
149
150
	/**
151
	 * List all installed apps
152
	 *
153
	 * @return string[]
154
	 */
155
	public function getInstalledApps() {
156
		return array_keys($this->getInstalledAppsValues());
157
	}
158
159
	/**
160
	 * List all apps enabled for a user
161
	 *
162
	 * @param \OCP\IUser $user
163
	 * @return string[]
164
	 */
165
	public function getEnabledAppsForUser(IUser $user) {
166
		$apps = $this->getInstalledAppsValues();
167
		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
168
			return $this->checkAppForUser($enabled, $user);
169
		});
170
		return array_keys($appsForUser);
171
	}
172
173
	/**
174
	 * @param \OCP\IGroup $group
175
	 * @return array
176
	 */
177
	public function getEnabledAppsForGroup(IGroup $group): array {
178
		$apps = $this->getInstalledAppsValues();
179
		$appsForGroups = array_filter($apps, function ($enabled) use ($group) {
180
			return $this->checkAppForGroups($enabled, $group);
181
		});
182
		return array_keys($appsForGroups);
183
	}
184
185
	/**
186
	 * @return array
187
	 */
188
	public function getAutoDisabledApps(): array {
189
		return $this->autoDisabledApps;
190
	}
191
192
	/**
193
	 * @param string $appId
194
	 * @return array
195
	 */
196
	public function getAppRestriction(string $appId): array {
197
		$values = $this->getInstalledAppsValues();
198
199
		if (!isset($values[$appId])) {
200
			return [];
201
		}
202
203
		if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
204
			return [];
205
		}
206
		return json_decode($values[$appId]);
207
	}
208
209
210
	/**
211
	 * Check if an app is enabled for user
212
	 *
213
	 * @param string $appId
214
	 * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
215
	 * @return bool
216
	 */
217
	public function isEnabledForUser($appId, $user = null) {
218
		if ($this->isAlwaysEnabled($appId)) {
219
			return true;
220
		}
221
		if ($user === null) {
222
			$user = $this->userSession->getUser();
223
		}
224
		$installedApps = $this->getInstalledAppsValues();
225
		if (isset($installedApps[$appId])) {
226
			return $this->checkAppForUser($installedApps[$appId], $user);
227
		} else {
228
			return false;
229
		}
230
	}
231
232
	/**
233
	 * @param string $enabled
234
	 * @param IUser $user
235
	 * @return bool
236
	 */
237
	private function checkAppForUser($enabled, $user) {
238
		if ($enabled === 'yes') {
239
			return true;
240
		} elseif ($user === null) {
241
			return false;
242
		} else {
243
			if(empty($enabled)){
244
				return false;
245
			}
246
247
			$groupIds = json_decode($enabled);
248
249
			if (!is_array($groupIds)) {
250
				$jsonError = json_last_error();
251
				$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
252
				return false;
253
			}
254
255
			$userGroups = $this->groupManager->getUserGroupIds($user);
256
			foreach ($userGroups as $groupId) {
257
				if (in_array($groupId, $groupIds, true)) {
258
					return true;
259
				}
260
			}
261
			return false;
262
		}
263
	}
264
265
	/**
266
	 * @param string $enabled
267
	 * @param IGroup $group
268
	 * @return bool
269
	 */
270
	private function checkAppForGroups(string $enabled, IGroup $group): bool {
271
		if ($enabled === 'yes') {
272
			return true;
273
		} elseif ($group === null) {
274
			return false;
275
		} else {
276
			if (empty($enabled)) {
277
				return false;
278
			}
279
280
			$groupIds = json_decode($enabled);
281
282
			if (!is_array($groupIds)) {
283
				$jsonError = json_last_error();
284
				$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError, ['app' => 'lib']);
285
				return false;
286
			}
287
288
			return in_array($group->getGID(), $groupIds);
289
		}
290
	}
291
292
	/**
293
	 * Check if an app is enabled in the instance
294
	 *
295
	 * Notice: This actually checks if the app is enabled and not only if it is installed.
296
	 *
297
	 * @param string $appId
298
	 * @param \OCP\IGroup[]|String[] $groups
299
	 * @return bool
300
	 */
301
	public function isInstalled($appId) {
302
		$installedApps = $this->getInstalledAppsValues();
303
		return isset($installedApps[$appId]);
304
	}
305
306
	public function ignoreNextcloudRequirementForApp(string $appId): void {
307
		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
308
		if (!in_array($appId, $ignoreMaxApps, true)) {
309
			$ignoreMaxApps[] = $appId;
310
			$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
311
		}
312
	}
313
314
	/**
315
	 * Enable an app for every user
316
	 *
317
	 * @param string $appId
318
	 * @param bool $forceEnable
319
	 * @throws AppPathNotFoundException
320
	 */
321
	public function enableApp(string $appId, bool $forceEnable = false): void {
322
		// Check if app exists
323
		$this->getAppPath($appId);
324
325
		if ($forceEnable) {
326
			$this->ignoreNextcloudRequirementForApp($appId);
327
		}
328
329
		$this->installedAppsCache[$appId] = 'yes';
330
		$this->appConfig->setValue($appId, 'enabled', 'yes');
331
		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
0 ignored issues
show
Bug introduced by
OCP\App\ManagerEvent::EVENT_APP_ENABLE of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

331
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new OCP\App\ManagerEvent...ENT_APP_ENABLE, $appId). ( Ignorable by Annotation )

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

331
		$this->dispatcher->/** @scrutinizer ignore-call */ 
332
                     dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
332
			ManagerEvent::EVENT_APP_ENABLE, $appId
333
		));
334
		$this->clearAppsCache();
335
	}
336
337
	/**
338
	 * Whether a list of types contains a protected app type
339
	 *
340
	 * @param string[] $types
341
	 * @return bool
342
	 */
343
	public function hasProtectedAppType($types) {
344
		if (empty($types)) {
345
			return false;
346
		}
347
348
		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
349
		return !empty($protectedTypes);
350
	}
351
352
	/**
353
	 * Enable an app only for specific groups
354
	 *
355
	 * @param string $appId
356
	 * @param \OCP\IGroup[] $groups
357
	 * @param bool $forceEnable
358
	 * @throws \InvalidArgumentException if app can't be enabled for groups
359
	 * @throws AppPathNotFoundException
360
	 */
361
	public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
362
		// Check if app exists
363
		$this->getAppPath($appId);
364
365
		$info = $this->getAppInfo($appId);
366
		if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
367
			throw new \InvalidArgumentException("$appId can't be enabled for groups.");
368
		}
369
370
		if ($forceEnable) {
371
			$this->ignoreNextcloudRequirementForApp($appId);
372
		}
373
374
		$groupIds = array_map(function ($group) {
375
			/** @var \OCP\IGroup $group */
376
			return ($group instanceof IGroup)
0 ignored issues
show
introduced by
$group is always a sub-type of OCP\IGroup.
Loading history...
377
				? $group->getGID()
378
				: $group;
379
		}, $groups);
380
381
		$this->installedAppsCache[$appId] = json_encode($groupIds);
382
		$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
383
		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
0 ignored issues
show
Bug introduced by
OCP\App\ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

383
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new OCP\App\ManagerEvent...ROUPS, $appId, $groups). ( Ignorable by Annotation )

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

383
		$this->dispatcher->/** @scrutinizer ignore-call */ 
384
                     dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
384
			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
385
		));
386
		$this->clearAppsCache();
387
388
	}
389
390
	/**
391
	 * Disable an app for every user
392
	 *
393
	 * @param string $appId
394
	 * @param bool $automaticDisabled
395
	 * @throws \Exception if app can't be disabled
396
	 */
397
	public function disableApp($appId, $automaticDisabled = false) {
398
		if ($this->isAlwaysEnabled($appId)) {
399
			throw new \Exception("$appId can't be disabled.");
400
		}
401
402
		if ($automaticDisabled) {
403
			$this->autoDisabledApps[] = $appId;
404
		}
405
406
		unset($this->installedAppsCache[$appId]);
407
		$this->appConfig->setValue($appId, 'enabled', 'no');
408
409
		// run uninstall steps
410
		$appData = $this->getAppInfo($appId);
411
		if (!is_null($appData)) {
412
			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
413
		}
414
415
		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new OCP\App\ManagerEvent...NT_APP_DISABLE, $appId). ( Ignorable by Annotation )

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

415
		$this->dispatcher->/** @scrutinizer ignore-call */ 
416
                     dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\App\ManagerEvent::EVENT_APP_DISABLE of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

415
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
Loading history...
416
			ManagerEvent::EVENT_APP_DISABLE, $appId
417
		));
418
		$this->clearAppsCache();
419
	}
420
421
	/**
422
	 * Get the directory for the given app.
423
	 *
424
	 * @param string $appId
425
	 * @return string
426
	 * @throws AppPathNotFoundException if app folder can't be found
427
	 */
428
	public function getAppPath($appId) {
429
		$appPath = \OC_App::getAppPath($appId);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

429
		$appPath = /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($appId);

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

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

Loading history...
430
		if($appPath === false) {
431
			throw new AppPathNotFoundException('Could not find path for ' . $appId);
432
		}
433
		return $appPath;
434
	}
435
436
	/**
437
	 * Get the web path for the given app.
438
	 *
439
	 * @param string $appId
440
	 * @return string
441
	 * @throws AppPathNotFoundException if app path can't be found
442
	 */
443
	public function getAppWebPath(string $appId): string {
444
		$appWebPath = \OC_App::getAppWebPath($appId);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppWebPath() has been deprecated: 18.0.0 use \OC::$server->getAppManager()->getAppWebPath() ( Ignorable by Annotation )

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

444
		$appWebPath = /** @scrutinizer ignore-deprecated */ \OC_App::getAppWebPath($appId);

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

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

Loading history...
445
		if($appWebPath === false) {
446
			throw new AppPathNotFoundException('Could not find web path for ' . $appId);
447
		}
448
		return $appWebPath;
449
	}
450
451
	/**
452
	 * Clear the cached list of apps when enabling/disabling an app
453
	 */
454
	public function clearAppsCache() {
455
		$settingsMemCache = $this->memCacheFactory->createDistributed('settings');
456
		$settingsMemCache->clear('listApps');
457
		$this->appInfos = [];
458
	}
459
460
	/**
461
	 * Returns a list of apps that need upgrade
462
	 *
463
	 * @param string $version Nextcloud version as array of version components
464
	 * @return array list of app info from apps that need an upgrade
465
	 *
466
	 * @internal
467
	 */
468
	public function getAppsNeedingUpgrade($version) {
469
		$appsToUpgrade = [];
470
		$apps = $this->getInstalledApps();
471
		foreach ($apps as $appId) {
472
			$appInfo = $this->getAppInfo($appId);
473
			$appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
474
			if ($appDbVersion
475
				&& isset($appInfo['version'])
476
				&& version_compare($appInfo['version'], $appDbVersion, '>')
477
				&& \OC_App::isAppCompatible($version, $appInfo)
478
			) {
479
				$appsToUpgrade[] = $appInfo;
480
			}
481
		}
482
483
		return $appsToUpgrade;
484
	}
485
486
	/**
487
	 * Returns the app information from "appinfo/info.xml".
488
	 *
489
	 * @param string $appId app id
490
	 *
491
	 * @param bool $path
492
	 * @param null $lang
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $lang is correct as it would always require null to be passed?
Loading history...
493
	 * @return array|null app info
494
	 */
495
	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
496
		if ($path) {
497
			$file = $appId;
498
		} else {
499
			if ($lang === null && isset($this->appInfos[$appId])) {
500
				return $this->appInfos[$appId];
501
			}
502
			try {
503
				$appPath = $this->getAppPath($appId);
504
			} catch (AppPathNotFoundException $e) {
505
				return null;
506
			}
507
			$file = $appPath . '/appinfo/info.xml';
508
		}
509
510
		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
511
		$data = $parser->parse($file);
512
513
		if (is_array($data)) {
514
			$data = \OC_App::parseAppInfo($data, $lang);
515
		}
516
517
		if ($lang === null) {
0 ignored issues
show
introduced by
The condition $lang === null is always true.
Loading history...
518
			$this->appInfos[$appId] = $data;
519
		}
520
521
		return $data;
522
	}
523
524
	public function getAppVersion(string $appId, bool $useCache = true): string {
525
		if(!$useCache || !isset($this->appVersions[$appId])) {
526
			$appInfo = $this->getAppInfo($appId);
527
			$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
528
		}
529
		return $this->appVersions[$appId];
530
	}
531
532
	/**
533
	 * Returns a list of apps incompatible with the given version
534
	 *
535
	 * @param string $version Nextcloud version as array of version components
536
	 *
537
	 * @return array list of app info from incompatible apps
538
	 *
539
	 * @internal
540
	 */
541
	public function getIncompatibleApps(string $version): array {
542
		$apps = $this->getInstalledApps();
543
		$incompatibleApps = array();
544
		foreach ($apps as $appId) {
545
			$info = $this->getAppInfo($appId);
546
			if ($info === null) {
547
				$incompatibleApps[] = ['id' => $appId];
548
			} else if (!\OC_App::isAppCompatible($version, $info)) {
549
				$incompatibleApps[] = $info;
550
			}
551
		}
552
		return $incompatibleApps;
553
	}
554
555
	/**
556
	 * @inheritdoc
557
	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
558
	 */
559
	public function isShipped($appId) {
560
		$this->loadShippedJson();
561
		return in_array($appId, $this->shippedApps, true);
562
	}
563
564
	private function isAlwaysEnabled($appId) {
565
		$alwaysEnabled = $this->getAlwaysEnabledApps();
566
		return in_array($appId, $alwaysEnabled, true);
567
	}
568
569
	/**
570
	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
571
	 * @throws \Exception
572
	 */
573
	private function loadShippedJson() {
574
		if ($this->shippedApps === null) {
575
			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
576
			if (!file_exists($shippedJson)) {
577
				throw new \Exception("File not found: $shippedJson");
578
			}
579
			$content = json_decode(file_get_contents($shippedJson), true);
580
			$this->shippedApps = $content['shippedApps'];
581
			$this->alwaysEnabled = $content['alwaysEnabled'];
582
		}
583
	}
584
585
	/**
586
	 * @inheritdoc
587
	 */
588
	public function getAlwaysEnabledApps() {
589
		$this->loadShippedJson();
590
		return $this->alwaysEnabled;
591
	}
592
}
593