Total Complexity | 134 |
Total Lines | 787 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like AppManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AppManager, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
64 | class AppManager implements IAppManager { |
||
65 | /** |
||
66 | * Apps with these types can not be enabled for certain groups only |
||
67 | * @var string[] |
||
68 | */ |
||
69 | protected $protectedAppTypes = [ |
||
70 | 'filesystem', |
||
71 | 'prelogin', |
||
72 | 'authentication', |
||
73 | 'logging', |
||
74 | 'prevent_group_restriction', |
||
75 | ]; |
||
76 | |||
77 | private IUserSession $userSession; |
||
78 | private IConfig $config; |
||
79 | private AppConfig $appConfig; |
||
80 | private IGroupManager $groupManager; |
||
81 | private ICacheFactory $memCacheFactory; |
||
82 | private EventDispatcherInterface $legacyDispatcher; |
||
83 | private IEventDispatcher $dispatcher; |
||
84 | private LoggerInterface $logger; |
||
85 | |||
86 | /** @var string[] $appId => $enabled */ |
||
87 | private array $installedAppsCache = []; |
||
88 | |||
89 | /** @var string[]|null */ |
||
90 | private ?array $shippedApps = null; |
||
91 | |||
92 | private array $alwaysEnabled = []; |
||
93 | private array $defaultEnabled = []; |
||
94 | |||
95 | /** @var array */ |
||
96 | private array $appInfos = []; |
||
97 | |||
98 | /** @var array */ |
||
99 | private array $appVersions = []; |
||
100 | |||
101 | /** @var array */ |
||
102 | private array $autoDisabledApps = []; |
||
103 | private array $appTypes = []; |
||
104 | |||
105 | /** @var array<string, true> */ |
||
106 | private array $loadedApps = []; |
||
107 | |||
108 | public function __construct(IUserSession $userSession, |
||
109 | IConfig $config, |
||
110 | AppConfig $appConfig, |
||
111 | IGroupManager $groupManager, |
||
112 | ICacheFactory $memCacheFactory, |
||
113 | EventDispatcherInterface $legacyDispatcher, |
||
114 | IEventDispatcher $dispatcher, |
||
115 | LoggerInterface $logger) { |
||
116 | $this->userSession = $userSession; |
||
117 | $this->config = $config; |
||
118 | $this->appConfig = $appConfig; |
||
119 | $this->groupManager = $groupManager; |
||
120 | $this->memCacheFactory = $memCacheFactory; |
||
121 | $this->legacyDispatcher = $legacyDispatcher; |
||
122 | $this->dispatcher = $dispatcher; |
||
123 | $this->logger = $logger; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * @return string[] $appId => $enabled |
||
128 | */ |
||
129 | private function getInstalledAppsValues(): array { |
||
130 | if (!$this->installedAppsCache) { |
||
|
|||
131 | $values = $this->appConfig->getValues(false, 'enabled'); |
||
132 | |||
133 | $alwaysEnabledApps = $this->getAlwaysEnabledApps(); |
||
134 | foreach ($alwaysEnabledApps as $appId) { |
||
135 | $values[$appId] = 'yes'; |
||
136 | } |
||
137 | |||
138 | $this->installedAppsCache = array_filter($values, function ($value) { |
||
139 | return $value !== 'no'; |
||
140 | }); |
||
141 | ksort($this->installedAppsCache); |
||
142 | } |
||
143 | return $this->installedAppsCache; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * List all installed apps |
||
148 | * |
||
149 | * @return string[] |
||
150 | */ |
||
151 | public function getInstalledApps() { |
||
152 | return array_keys($this->getInstalledAppsValues()); |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * List all apps enabled for a user |
||
157 | * |
||
158 | * @param \OCP\IUser $user |
||
159 | * @return string[] |
||
160 | */ |
||
161 | public function getEnabledAppsForUser(IUser $user) { |
||
162 | $apps = $this->getInstalledAppsValues(); |
||
163 | $appsForUser = array_filter($apps, function ($enabled) use ($user) { |
||
164 | return $this->checkAppForUser($enabled, $user); |
||
165 | }); |
||
166 | return array_keys($appsForUser); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * @param IGroup $group |
||
171 | * @return array |
||
172 | */ |
||
173 | public function getEnabledAppsForGroup(IGroup $group): array { |
||
174 | $apps = $this->getInstalledAppsValues(); |
||
175 | $appsForGroups = array_filter($apps, function ($enabled) use ($group) { |
||
176 | return $this->checkAppForGroups($enabled, $group); |
||
177 | }); |
||
178 | return array_keys($appsForGroups); |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Loads all apps |
||
183 | * |
||
184 | * @param string[] $types |
||
185 | * @return bool |
||
186 | * |
||
187 | * This function walks through the Nextcloud directory and loads all apps |
||
188 | * it can find. A directory contains an app if the file /appinfo/info.xml |
||
189 | * exists. |
||
190 | * |
||
191 | * if $types is set to non-empty array, only apps of those types will be loaded |
||
192 | */ |
||
193 | public function loadApps(array $types = []): bool { |
||
194 | if ($this->config->getSystemValueBool('maintenance', false)) { |
||
195 | return false; |
||
196 | } |
||
197 | // Load the enabled apps here |
||
198 | $apps = \OC_App::getEnabledApps(); |
||
199 | |||
200 | // Add each apps' folder as allowed class path |
||
201 | foreach ($apps as $app) { |
||
202 | // If the app is already loaded then autoloading it makes no sense |
||
203 | if (!$this->isAppLoaded($app)) { |
||
204 | $path = \OC_App::getAppPath($app); |
||
205 | if ($path !== false) { |
||
206 | \OC_App::registerAutoloading($app, $path); |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | // prevent app.php from printing output |
||
212 | ob_start(); |
||
213 | foreach ($apps as $app) { |
||
214 | if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) { |
||
215 | try { |
||
216 | $this->loadApp($app); |
||
217 | } catch (\Throwable $e) { |
||
218 | $this->logger->emergency('Error during app loading: ' . $e->getMessage(), [ |
||
219 | 'exception' => $e, |
||
220 | 'app' => $app, |
||
221 | ]); |
||
222 | } |
||
223 | } |
||
224 | } |
||
225 | ob_end_clean(); |
||
226 | |||
227 | return true; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * check if an app is of a specific type |
||
232 | * |
||
233 | * @param string $app |
||
234 | * @param array $types |
||
235 | * @return bool |
||
236 | */ |
||
237 | public function isType(string $app, array $types): bool { |
||
238 | $appTypes = $this->getAppTypes($app); |
||
239 | foreach ($types as $type) { |
||
240 | if (in_array($type, $appTypes, true)) { |
||
241 | return true; |
||
242 | } |
||
243 | } |
||
244 | return false; |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * get the types of an app |
||
249 | * |
||
250 | * @param string $app |
||
251 | * @return string[] |
||
252 | */ |
||
253 | private function getAppTypes(string $app): array { |
||
254 | //load the cache |
||
255 | if (count($this->appTypes) === 0) { |
||
256 | $this->appTypes = $this->appConfig->getValues(false, 'types') ?: []; |
||
257 | } |
||
258 | |||
259 | if (isset($this->appTypes[$app])) { |
||
260 | return explode(',', $this->appTypes[$app]); |
||
261 | } |
||
262 | |||
263 | return []; |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * @return array |
||
268 | */ |
||
269 | public function getAutoDisabledApps(): array { |
||
270 | return $this->autoDisabledApps; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * @param string $appId |
||
275 | * @return array |
||
276 | */ |
||
277 | public function getAppRestriction(string $appId): array { |
||
278 | $values = $this->getInstalledAppsValues(); |
||
279 | |||
280 | if (!isset($values[$appId])) { |
||
281 | return []; |
||
282 | } |
||
283 | |||
284 | if ($values[$appId] === 'yes' || $values[$appId] === 'no') { |
||
285 | return []; |
||
286 | } |
||
287 | return json_decode($values[$appId], true); |
||
288 | } |
||
289 | |||
290 | |||
291 | /** |
||
292 | * Check if an app is enabled for user |
||
293 | * |
||
294 | * @param string $appId |
||
295 | * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used |
||
296 | * @return bool |
||
297 | */ |
||
298 | public function isEnabledForUser($appId, $user = null) { |
||
299 | if ($this->isAlwaysEnabled($appId)) { |
||
300 | return true; |
||
301 | } |
||
302 | if ($user === null) { |
||
303 | $user = $this->userSession->getUser(); |
||
304 | } |
||
305 | $installedApps = $this->getInstalledAppsValues(); |
||
306 | if (isset($installedApps[$appId])) { |
||
307 | return $this->checkAppForUser($installedApps[$appId], $user); |
||
308 | } else { |
||
309 | return false; |
||
310 | } |
||
311 | } |
||
312 | |||
313 | private function checkAppForUser(string $enabled, ?IUser $user): bool { |
||
314 | if ($enabled === 'yes') { |
||
315 | return true; |
||
316 | } elseif ($user === null) { |
||
317 | return false; |
||
318 | } else { |
||
319 | if (empty($enabled)) { |
||
320 | return false; |
||
321 | } |
||
322 | |||
323 | $groupIds = json_decode($enabled); |
||
324 | |||
325 | if (!is_array($groupIds)) { |
||
326 | $jsonError = json_last_error(); |
||
327 | $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError); |
||
328 | return false; |
||
329 | } |
||
330 | |||
331 | $userGroups = $this->groupManager->getUserGroupIds($user); |
||
332 | foreach ($userGroups as $groupId) { |
||
333 | if (in_array($groupId, $groupIds, true)) { |
||
334 | return true; |
||
335 | } |
||
336 | } |
||
337 | return false; |
||
338 | } |
||
339 | } |
||
340 | |||
341 | private function checkAppForGroups(string $enabled, IGroup $group): bool { |
||
342 | if ($enabled === 'yes') { |
||
343 | return true; |
||
344 | } else { |
||
345 | if (empty($enabled)) { |
||
346 | return false; |
||
347 | } |
||
348 | |||
349 | $groupIds = json_decode($enabled); |
||
350 | |||
351 | if (!is_array($groupIds)) { |
||
352 | $jsonError = json_last_error(); |
||
353 | $this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError); |
||
354 | return false; |
||
355 | } |
||
356 | |||
357 | return in_array($group->getGID(), $groupIds); |
||
358 | } |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Check if an app is enabled in the instance |
||
363 | * |
||
364 | * Notice: This actually checks if the app is enabled and not only if it is installed. |
||
365 | * |
||
366 | * @param string $appId |
||
367 | * @param IGroup[]|String[] $groups |
||
368 | * @return bool |
||
369 | */ |
||
370 | public function isInstalled($appId) { |
||
371 | $installedApps = $this->getInstalledAppsValues(); |
||
372 | return isset($installedApps[$appId]); |
||
373 | } |
||
374 | |||
375 | public function ignoreNextcloudRequirementForApp(string $appId): void { |
||
376 | $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []); |
||
377 | if (!in_array($appId, $ignoreMaxApps, true)) { |
||
378 | $ignoreMaxApps[] = $appId; |
||
379 | $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps); |
||
380 | } |
||
381 | } |
||
382 | |||
383 | public function loadApp(string $app): void { |
||
384 | if (isset($this->loadedApps[$app])) { |
||
385 | return; |
||
386 | } |
||
387 | $this->loadedApps[$app] = true; |
||
388 | $appPath = \OC_App::getAppPath($app); |
||
389 | if ($appPath === false) { |
||
390 | return; |
||
391 | } |
||
392 | $eventLogger = \OC::$server->get(\OCP\Diagnostics\IEventLogger::class); |
||
393 | $eventLogger->start("bootstrap:load_app:$app", "Load $app"); |
||
394 | |||
395 | // in case someone calls loadApp() directly |
||
396 | \OC_App::registerAutoloading($app, $appPath); |
||
397 | |||
398 | /** @var Coordinator $coordinator */ |
||
399 | $coordinator = \OC::$server->get(Coordinator::class); |
||
400 | $isBootable = $coordinator->isBootable($app); |
||
401 | |||
402 | $hasAppPhpFile = is_file($appPath . '/appinfo/app.php'); |
||
403 | |||
404 | $eventLogger = \OC::$server->get(IEventLogger::class); |
||
405 | $eventLogger->start('bootstrap:load_app_' . $app, 'Load app: ' . $app); |
||
406 | if ($isBootable && $hasAppPhpFile) { |
||
407 | $this->logger->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [ |
||
408 | 'app' => $app, |
||
409 | ]); |
||
410 | } elseif ($hasAppPhpFile) { |
||
411 | $eventLogger->start("bootstrap:load_app:$app:app.php", "Load legacy app.php app $app"); |
||
412 | $this->logger->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ |
||
413 | 'app' => $app, |
||
414 | ]); |
||
415 | try { |
||
416 | self::requireAppFile($appPath); |
||
417 | } catch (\Throwable $ex) { |
||
418 | if ($ex instanceof ServerNotAvailableException) { |
||
419 | throw $ex; |
||
420 | } |
||
421 | if (!$this->isShipped($app) && !$this->isType($app, ['authentication'])) { |
||
422 | $this->logger->error("App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(), [ |
||
423 | 'exception' => $ex, |
||
424 | ]); |
||
425 | |||
426 | // Only disable apps which are not shipped and that are not authentication apps |
||
427 | $this->disableApp($app, true); |
||
428 | } else { |
||
429 | $this->logger->error("App $app threw an error during app.php load: " . $ex->getMessage(), [ |
||
430 | 'exception' => $ex, |
||
431 | ]); |
||
432 | } |
||
433 | } |
||
434 | $eventLogger->end("bootstrap:load_app:$app:app.php"); |
||
435 | } |
||
436 | |||
437 | $coordinator->bootApp($app); |
||
438 | |||
439 | $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it"); |
||
440 | $info = $this->getAppInfo($app); |
||
441 | if (!empty($info['activity'])) { |
||
442 | $activityManager = \OC::$server->get(IActivityManager::class); |
||
443 | if (!empty($info['activity']['filters'])) { |
||
444 | foreach ($info['activity']['filters'] as $filter) { |
||
445 | $activityManager->registerFilter($filter); |
||
446 | } |
||
447 | } |
||
448 | if (!empty($info['activity']['settings'])) { |
||
449 | foreach ($info['activity']['settings'] as $setting) { |
||
450 | $activityManager->registerSetting($setting); |
||
451 | } |
||
452 | } |
||
453 | if (!empty($info['activity']['providers'])) { |
||
454 | foreach ($info['activity']['providers'] as $provider) { |
||
455 | $activityManager->registerProvider($provider); |
||
456 | } |
||
457 | } |
||
458 | } |
||
459 | |||
460 | if (!empty($info['settings'])) { |
||
461 | $settingsManager = \OC::$server->get(ISettingsManager::class); |
||
462 | if (!empty($info['settings']['admin'])) { |
||
463 | foreach ($info['settings']['admin'] as $setting) { |
||
464 | $settingsManager->registerSetting('admin', $setting); |
||
465 | } |
||
466 | } |
||
467 | if (!empty($info['settings']['admin-section'])) { |
||
468 | foreach ($info['settings']['admin-section'] as $section) { |
||
469 | $settingsManager->registerSection('admin', $section); |
||
470 | } |
||
471 | } |
||
472 | if (!empty($info['settings']['personal'])) { |
||
473 | foreach ($info['settings']['personal'] as $setting) { |
||
474 | $settingsManager->registerSetting('personal', $setting); |
||
475 | } |
||
476 | } |
||
477 | if (!empty($info['settings']['personal-section'])) { |
||
478 | foreach ($info['settings']['personal-section'] as $section) { |
||
479 | $settingsManager->registerSection('personal', $section); |
||
480 | } |
||
481 | } |
||
482 | } |
||
483 | |||
484 | if (!empty($info['collaboration']['plugins'])) { |
||
485 | // deal with one or many plugin entries |
||
486 | $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ? |
||
487 | [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin']; |
||
488 | $collaboratorSearch = null; |
||
489 | $autoCompleteManager = null; |
||
490 | foreach ($plugins as $plugin) { |
||
491 | if ($plugin['@attributes']['type'] === 'collaborator-search') { |
||
492 | $pluginInfo = [ |
||
493 | 'shareType' => $plugin['@attributes']['share-type'], |
||
494 | 'class' => $plugin['@value'], |
||
495 | ]; |
||
496 | $collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class); |
||
497 | $collaboratorSearch->registerPlugin($pluginInfo); |
||
498 | } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') { |
||
499 | $autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class); |
||
500 | $autoCompleteManager->registerSorter($plugin['@value']); |
||
501 | } |
||
502 | } |
||
503 | } |
||
504 | $eventLogger->end("bootstrap:load_app:$app:info"); |
||
505 | |||
506 | $eventLogger->end("bootstrap:load_app:$app"); |
||
507 | } |
||
508 | /** |
||
509 | * Check if an app is loaded |
||
510 | * @param string $app app id |
||
511 | * @since 26.0.0 |
||
512 | */ |
||
513 | public function isAppLoaded(string $app): bool { |
||
514 | return isset($this->loadedApps[$app]); |
||
515 | } |
||
516 | |||
517 | /** |
||
518 | * Load app.php from the given app |
||
519 | * |
||
520 | * @param string $app app name |
||
521 | * @throws \Error |
||
522 | */ |
||
523 | private static function requireAppFile(string $app): void { |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Enable an app for every user |
||
530 | * |
||
531 | * @param string $appId |
||
532 | * @param bool $forceEnable |
||
533 | * @throws AppPathNotFoundException |
||
534 | */ |
||
535 | public function enableApp(string $appId, bool $forceEnable = false): void { |
||
536 | // Check if app exists |
||
537 | $this->getAppPath($appId); |
||
538 | |||
539 | if ($forceEnable) { |
||
540 | $this->ignoreNextcloudRequirementForApp($appId); |
||
541 | } |
||
542 | |||
543 | $this->installedAppsCache[$appId] = 'yes'; |
||
544 | $this->appConfig->setValue($appId, 'enabled', 'yes'); |
||
545 | $this->dispatcher->dispatchTyped(new AppEnableEvent($appId)); |
||
546 | $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent( |
||
547 | ManagerEvent::EVENT_APP_ENABLE, $appId |
||
548 | )); |
||
549 | $this->clearAppsCache(); |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Whether a list of types contains a protected app type |
||
554 | * |
||
555 | * @param string[] $types |
||
556 | * @return bool |
||
557 | */ |
||
558 | public function hasProtectedAppType($types) { |
||
559 | if (empty($types)) { |
||
560 | return false; |
||
561 | } |
||
562 | |||
563 | $protectedTypes = array_intersect($this->protectedAppTypes, $types); |
||
564 | return !empty($protectedTypes); |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * Enable an app only for specific groups |
||
569 | * |
||
570 | * @param string $appId |
||
571 | * @param IGroup[] $groups |
||
572 | * @param bool $forceEnable |
||
573 | * @throws \InvalidArgumentException if app can't be enabled for groups |
||
574 | * @throws AppPathNotFoundException |
||
575 | */ |
||
576 | public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void { |
||
577 | // Check if app exists |
||
578 | $this->getAppPath($appId); |
||
579 | |||
580 | $info = $this->getAppInfo($appId); |
||
581 | if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) { |
||
582 | throw new \InvalidArgumentException("$appId can't be enabled for groups."); |
||
583 | } |
||
584 | |||
585 | if ($forceEnable) { |
||
586 | $this->ignoreNextcloudRequirementForApp($appId); |
||
587 | } |
||
588 | |||
589 | /** @var string[] $groupIds */ |
||
590 | $groupIds = array_map(function ($group) { |
||
591 | /** @var IGroup $group */ |
||
592 | return ($group instanceof IGroup) |
||
593 | ? $group->getGID() |
||
594 | : $group; |
||
595 | }, $groups); |
||
596 | |||
597 | $this->installedAppsCache[$appId] = json_encode($groupIds); |
||
598 | $this->appConfig->setValue($appId, 'enabled', json_encode($groupIds)); |
||
599 | $this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds)); |
||
600 | $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent( |
||
601 | ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups |
||
602 | )); |
||
603 | $this->clearAppsCache(); |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Disable an app for every user |
||
608 | * |
||
609 | * @param string $appId |
||
610 | * @param bool $automaticDisabled |
||
611 | * @throws \Exception if app can't be disabled |
||
612 | */ |
||
613 | public function disableApp($appId, $automaticDisabled = false) { |
||
614 | if ($this->isAlwaysEnabled($appId)) { |
||
615 | throw new \Exception("$appId can't be disabled."); |
||
616 | } |
||
617 | |||
618 | if ($automaticDisabled) { |
||
619 | $previousSetting = $this->appConfig->getValue($appId, 'enabled', 'yes'); |
||
620 | if ($previousSetting !== 'yes' && $previousSetting !== 'no') { |
||
621 | $previousSetting = json_decode($previousSetting, true); |
||
622 | } |
||
623 | $this->autoDisabledApps[$appId] = $previousSetting; |
||
624 | } |
||
625 | |||
626 | unset($this->installedAppsCache[$appId]); |
||
627 | $this->appConfig->setValue($appId, 'enabled', 'no'); |
||
628 | |||
629 | // run uninstall steps |
||
630 | $appData = $this->getAppInfo($appId); |
||
631 | if (!is_null($appData)) { |
||
632 | \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']); |
||
633 | } |
||
634 | |||
635 | $this->dispatcher->dispatchTyped(new AppDisableEvent($appId)); |
||
636 | $this->legacyDispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent( |
||
637 | ManagerEvent::EVENT_APP_DISABLE, $appId |
||
638 | )); |
||
639 | $this->clearAppsCache(); |
||
640 | } |
||
641 | |||
642 | /** |
||
643 | * Get the directory for the given app. |
||
644 | * |
||
645 | * @param string $appId |
||
646 | * @return string |
||
647 | * @throws AppPathNotFoundException if app folder can't be found |
||
648 | */ |
||
649 | public function getAppPath($appId) { |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Get the web path for the given app. |
||
659 | * |
||
660 | * @param string $appId |
||
661 | * @return string |
||
662 | * @throws AppPathNotFoundException if app path can't be found |
||
663 | */ |
||
664 | public function getAppWebPath(string $appId): string { |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * Clear the cached list of apps when enabling/disabling an app |
||
674 | */ |
||
675 | public function clearAppsCache() { |
||
676 | $this->appInfos = []; |
||
677 | } |
||
678 | |||
679 | /** |
||
680 | * Returns a list of apps that need upgrade |
||
681 | * |
||
682 | * @param string $version Nextcloud version as array of version components |
||
683 | * @return array list of app info from apps that need an upgrade |
||
684 | * |
||
685 | * @internal |
||
686 | */ |
||
687 | public function getAppsNeedingUpgrade($version) { |
||
688 | $appsToUpgrade = []; |
||
689 | $apps = $this->getInstalledApps(); |
||
690 | foreach ($apps as $appId) { |
||
691 | $appInfo = $this->getAppInfo($appId); |
||
692 | $appDbVersion = $this->appConfig->getValue($appId, 'installed_version'); |
||
693 | if ($appDbVersion |
||
694 | && isset($appInfo['version']) |
||
695 | && version_compare($appInfo['version'], $appDbVersion, '>') |
||
696 | && \OC_App::isAppCompatible($version, $appInfo) |
||
697 | ) { |
||
698 | $appsToUpgrade[] = $appInfo; |
||
699 | } |
||
700 | } |
||
701 | |||
702 | return $appsToUpgrade; |
||
703 | } |
||
704 | |||
705 | /** |
||
706 | * Returns the app information from "appinfo/info.xml". |
||
707 | * |
||
708 | * @param string $appId app id |
||
709 | * |
||
710 | * @param bool $path |
||
711 | * @param null $lang |
||
712 | * @return array|null app info |
||
713 | */ |
||
714 | public function getAppInfo(string $appId, bool $path = false, $lang = null) { |
||
715 | if ($path) { |
||
716 | $file = $appId; |
||
717 | } else { |
||
718 | if ($lang === null && isset($this->appInfos[$appId])) { |
||
719 | return $this->appInfos[$appId]; |
||
720 | } |
||
721 | try { |
||
722 | $appPath = $this->getAppPath($appId); |
||
723 | } catch (AppPathNotFoundException $e) { |
||
724 | return null; |
||
725 | } |
||
726 | $file = $appPath . '/appinfo/info.xml'; |
||
727 | } |
||
728 | |||
729 | $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo')); |
||
730 | $data = $parser->parse($file); |
||
731 | |||
732 | if (is_array($data)) { |
||
733 | $data = \OC_App::parseAppInfo($data, $lang); |
||
734 | } |
||
735 | |||
736 | if ($lang === null) { |
||
737 | $this->appInfos[$appId] = $data; |
||
738 | } |
||
739 | |||
740 | return $data; |
||
741 | } |
||
742 | |||
743 | public function getAppVersion(string $appId, bool $useCache = true): string { |
||
744 | if (!$useCache || !isset($this->appVersions[$appId])) { |
||
745 | $appInfo = $this->getAppInfo($appId); |
||
746 | $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0'; |
||
747 | } |
||
748 | return $this->appVersions[$appId]; |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Returns a list of apps incompatible with the given version |
||
753 | * |
||
754 | * @param string $version Nextcloud version as array of version components |
||
755 | * |
||
756 | * @return array list of app info from incompatible apps |
||
757 | * |
||
758 | * @internal |
||
759 | */ |
||
760 | public function getIncompatibleApps(string $version): array { |
||
761 | $apps = $this->getInstalledApps(); |
||
762 | $incompatibleApps = []; |
||
763 | foreach ($apps as $appId) { |
||
764 | $info = $this->getAppInfo($appId); |
||
765 | if ($info === null) { |
||
766 | $incompatibleApps[] = ['id' => $appId, 'name' => $appId]; |
||
767 | } elseif (!\OC_App::isAppCompatible($version, $info)) { |
||
768 | $incompatibleApps[] = $info; |
||
769 | } |
||
770 | } |
||
771 | return $incompatibleApps; |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * @inheritdoc |
||
776 | * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped() |
||
777 | */ |
||
778 | public function isShipped($appId) { |
||
781 | } |
||
782 | |||
783 | private function isAlwaysEnabled(string $appId): bool { |
||
784 | $alwaysEnabled = $this->getAlwaysEnabledApps(); |
||
785 | return in_array($appId, $alwaysEnabled, true); |
||
786 | } |
||
787 | |||
788 | /** |
||
789 | * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson() |
||
790 | * @throws \Exception |
||
791 | */ |
||
792 | private function loadShippedJson(): void { |
||
793 | if ($this->shippedApps === null) { |
||
794 | $shippedJson = \OC::$SERVERROOT . '/core/shipped.json'; |
||
795 | if (!file_exists($shippedJson)) { |
||
796 | throw new \Exception("File not found: $shippedJson"); |
||
797 | } |
||
798 | $content = json_decode(file_get_contents($shippedJson), true); |
||
799 | $this->shippedApps = $content['shippedApps']; |
||
800 | $this->alwaysEnabled = $content['alwaysEnabled']; |
||
801 | $this->defaultEnabled = $content['defaultEnabled']; |
||
802 | } |
||
803 | } |
||
804 | |||
805 | /** |
||
806 | * @inheritdoc |
||
807 | */ |
||
808 | public function getAlwaysEnabledApps() { |
||
809 | $this->loadShippedJson(); |
||
810 | return $this->alwaysEnabled; |
||
811 | } |
||
812 | |||
813 | /** |
||
814 | * @inheritdoc |
||
815 | */ |
||
816 | public function isDefaultEnabled(string $appId): bool { |
||
818 | } |
||
819 | |||
820 | /** |
||
821 | * @inheritdoc |
||
822 | */ |
||
823 | public function getDefaultEnabledApps():array { |
||
827 | } |
||
828 | |||
829 | public function getDefaultAppForUser(?IUser $user = null): string { |
||
851 | } |
||
852 | } |
||
853 |
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.