You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
6.9 KiB
227 lines
6.9 KiB
<?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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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::UNIVERSITY, Preset::SMALL, Preset::MEDIUM, Preset::LARGE => ['enabled' => ['user_status', 'intros', 'guests'], 'disabled' => []],
|
|
Preset::SHARED => ['enabled' => ['intros', 'external'], 'disabled' => ['user_status']],
|
|
default => ['enabled' => [], 'disabled' => []],
|
|
};
|
|
}
|
|
}
|