← Back
Editing: PresetManager.php
<?php declare(strict_types=1); /** * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Config; use OC\App\AppManager; use OC\AppConfig; use OC\Installer; use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Preset; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; use OCP\IConfig; use OCP\Server; use Psr\Log\LoggerInterface; /** * tools to manage the Preset feature */ class PresetManager { private const PRESET_CONFIGKEY = 'config_preset'; private ?AppManager $appManager = null; private ?Installer $installer = null; private ?Preset $configLexiconPreset = null; public function __construct( private readonly IConfig $config, private readonly ConfigManager $configManager, private readonly LoggerInterface $logger, ) { } /** * store in config.php the new preset * refresh cached preset */ public function setLexiconPreset(Preset $preset): void { $this->config->setSystemValue(self::PRESET_CONFIGKEY, $preset->value); $this->configLexiconPreset = $preset; $this->configManager->clearConfigCaches(); $this->refreshPresetApps(); } /** * returns currently selected Preset */ public function getLexiconPreset(): Preset { if ($this->configLexiconPreset === null) { $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(self::PRESET_CONFIGKEY, 0)) ?? Preset::NONE; } return $this->configLexiconPreset; } /** * get lexicon config entries affected by Preset and its default values * * **Warning** This method MUST be considered resource-needy! * * @return array<string, list<array{config: string, defaults: array{CLUB: null|string, FAMILY: null|string, LARGE: null|string, MEDIUM: null|string, NONE: null|string, PRIVATE: null|string, SCHOOL: null|string, SHARED: null|string, SMALL: null|string, UNIVERSITY: null|string}, entry: array{definition: string, deprecated: bool, key: string, lazy: bool, note: string, type: 'ARRAY'|'BOOL'|'FLOAT'|'INT'|'MIXED'|'STRING'}, value?: mixed}>> */ public function retrieveLexiconPreset(?string $appId = null): array { if ($appId === null) { $apps = []; foreach (['core'] + Server::get(IAppManager::class)->getEnabledApps() as $app) { $preset = $this->retrieveLexiconPreset($app); $apps[$app] = $preset[$app]; } return $apps; } return [ $appId => array_merge( $this->extractLexiconPresetFromConfigClass($appId, 'app', Server::get(IAppConfig::class)), $this->extractLexiconPresetFromConfigClass($appId, 'user', Server::get(IUserConfig::class)) ), ]; } /** * @param string $appId * * @return list<array{config: string, defaults: array{CLUB: null|string, FAMILY: null|string, LARGE: null|string, MEDIUM: null|string, NONE: null|string, PRIVATE: null|string, SCHOOL: null|string, SHARED: null|string, SMALL: null|string, UNIVERSITY: null|string}, entry: array{definition: string, deprecated: bool, key: string, lazy: bool, note: string, type: 'ARRAY'|'BOOL'|'FLOAT'|'INT'|'MIXED'|'STRING'}, value?: mixed}> */ private function extractLexiconPresetFromConfigClass( string $appId, string $configType, AppConfig|UserConfig $config, ): array { $presets = []; $lexicon = $config->getConfigDetailsFromLexicon($appId); foreach ($lexicon['entries'] as $entry) { $defaults = []; foreach (Preset::cases() as $case) { // for each case, we need to use a fresh IAppConfig with clear cache // cloning to avoid conflict while emulating preset $newConfig = clone $config; if ($newConfig instanceof AppConfig) { // needed to ignore cache and rebuild default $newConfig->clearCache(); } if ($newConfig instanceof UserConfig) { // in the case of IUserConfig, clear all users' cache $newConfig->clearCacheAll(); } $newLexicon = $newConfig->getLexiconEntry($appId, $entry->getKey()); $defaults[$case->name] = $newLexicon?->getDefault($case); } // compare all value from $defaults, if more than 1 exist we have a preset $uniqueness = array_unique($defaults); if (count($uniqueness) < 2) { continue; } $details = [ 'config' => $configType, 'entry' => [ 'key' => $entry->getKey(), 'type' => $entry->getValueType()->name, 'definition' => $entry->getDefinition(), 'lazy' => $entry->isLazy(), 'deprecated' => $entry->isDeprecated(), 'note' => $entry->getNote(), ], 'defaults' => $defaults ]; try { // not interested if a users config value is already set if ($config instanceof AppConfig) { $details['value'] = $config->getDetails($appId, $entry->getKey())['value']; } } catch (AppConfigUnknownKeyException) { } $presets[] = $details; } return $presets; } /** * Enable and/or Disable a list of apps based on the currently selected Preset */ public function refreshPresetApps(): void { $this->loadAppManager(); $apps = $this->getPresetApps($this->getLexiconPreset()); foreach ($apps['disabled'] ?? [] as $app) { try { $this->appManager->disableApp($app); } catch (\Exception $e) { $this->logger->warning('could not disable app', ['exception' => $e]); } } foreach ($apps['enabled'] ?? [] as $app) { $this->installApp($app); } } /** * some parts cannot be initiated at __construct() time */ private function loadAppManager(): void { if ($this->appManager === null) { $this->appManager = Server::get(IAppManager::class); } if ($this->installer === null) { $this->installer = Server::get(Installer::class); } } /** * download, install and enable app. * generate warning entry in logs in case of failure. */ private function installApp(string $appId): void { $this->loadAppManager(); if (!$this->installer->isDownloaded($appId)) { try { $this->installer->downloadApp($appId); } catch (\Exception $e) { $this->logger->warning('could not download app', ['appId' => $appId, 'exception' => $e]); return; } } try { $this->installer->installApp($appId, true); } catch (\Exception $e) { $this->logger->warning('could not install app', ['appId' => $appId, 'exception' => $e]); return; } try { $this->appManager->enableApp($appId); } catch (AppPathNotFoundException $e) { $this->logger->warning('could not enable app', ['appId' => $appId, 'exception' => $e]); return; } } /** * return list of apps that are enabled/disabled when switching current Preset * * @return array<string, array{disabled: list<string>, enabled: list<string>}> */ public function retrieveLexiconPresetApps(): array { $apps = []; foreach (Preset::cases() as $case) { $apps[$case->name] = $this->getPresetApps($case); } return $apps; } /** * get listing of enabled/disabled app from Preset * * @return array{enabled: list<string>, disabled: list<string>} */ private function getPresetApps(Preset $preset): array { return match ($preset) { Preset::CLUB, Preset::FAMILY, Preset::SCHOOL, Preset::SMALL, Preset::MEDIUM => [ 'enabled' => ['user_status','guests','twofactor_backupcodes','twofactor_totp','twofactor_webauthn'], 'disabled' => []], Preset::UNIVERSITY, Preset::LARGE => [ 'enabled' => ['user_status','guests'], 'disabled' => []], Preset::SHARED => [ 'enabled' => ['external','twofactor_backupcodes','twofactor_totp','twofactor_webauthn'], 'disabled' => ['user_status']], Preset::PRIVATE => [ 'enabled' => ['twofactor_backupcodes','twofactor_totp','twofactor_webauthn'], 'disabled' => []], default => ['enabled' => [], 'disabled' => []], }; } }
Save File
Cancel