settingsMetadataGateway->getValue(SettingKeys::APP_TITLE_KEY); $value = $value !== null ? trim($value) : null; return $value !== '' ? $value : null; } public function setAppTitle(?string $title, ?string $description = null): bool { $value = $title !== null ? trim($title) : null; if ($value === '') { $value = null; } $desc = $description ?? 'setting.app_title'; return $this->settingsMetadataGateway->set(SettingKeys::APP_TITLE_KEY, $value, $desc); } public function getAppLocale(): ?string { $value = $this->settingsMetadataGateway->getValue(SettingKeys::APP_LOCALE_KEY); $value = $value !== null ? trim($value) : ''; return $value !== '' ? $value : null; } public function setAppLocale(?string $locale, ?string $description = null): bool { $value = $locale !== null ? trim($locale) : ''; if ($value === '') { $value = null; } else { $allowed = defined('APP_LOCALES') ? APP_LOCALES : []; if ($allowed && !in_array($value, $allowed, true)) { return false; } } $desc = $description ?? 'setting.app_locale'; return $this->settingsMetadataGateway->set(SettingKeys::APP_LOCALE_KEY, $value, $desc); } public function isRegistrationEnabled(): bool { return settingToBool( $this->settingsMetadataGateway->getValue(SettingKeys::APP_REGISTRATION_KEY), true ); } public function setRegistrationEnabled(bool $allowed, ?string $description = null): bool { $value = $allowed ? '1' : '0'; $desc = $description ?? 'setting.app_registration'; return $this->settingsMetadataGateway->set(SettingKeys::APP_REGISTRATION_KEY, $value, $desc); } /** * Catalog of available themes — purely code-level (config/themes.php), * not a global setting. * * @return array */ public function listThemes(): array { return $this->themeConfigService->all(); } public function isAllowedTheme(?string $theme): bool { $value = strtolower(trim((string) $theme)); return $value !== '' && in_array($value, $this->allowedThemes(), true); } /** * Default theme for flows that need ONE value without a tenant context * (user creation defaults, SSO-linked account provisioning, etc.). * Hardcoded to 'light' with a catalog fallback — no global setting. */ public function resolveDefaultTheme(): string { $allowed = $this->allowedThemes(); if (in_array('light', $allowed, true)) { return 'light'; } return (string) ($allowed[0] ?? 'light'); } public function normalizeTheme(?string $theme): string { $value = strtolower(trim((string) $theme)); if ($this->isAllowedTheme($value)) { return $value; } return $this->resolveDefaultTheme(); } public function normalizeLocale(?string $locale): string { $value = strtolower(trim((string) $locale)); $available = defined('APP_LOCALES') ? APP_LOCALES : [I18n::$defaultLocale]; if ($value === '' || !in_array($value, $available, true)) { return I18n::$locale ?? I18n::$defaultLocale; } return $value; } /** * @return list */ private function allowedThemes(): array { return array_keys($this->listThemes()); } }