refactor(settings): remove global appearance settings — tenant is sole source
Removes the global app_theme, app_theme_user and app_primary_color settings from the admin/settings area and the underlying service/cache/API/i18n/docs layers. The tenant columns (tenants.primary_color, default_theme, allow_user_theme) become the single source of truth for appearance. Branding helpers are tenant-only and fall back to a hardcoded 'light' theme with no brand color when no tenant context is available (login, error, pre-session pages). The APP_THEME env var is removed. Scope: - UI: Appearance tab gone from /admin/settings; tenant edit form drops the global-fallback color preview. - Service: SettingsAppGateway no longer exposes setting-backed accessors for theme/user-theme/primary-color. Theme catalog utilities (listThemes, isAllowedTheme, normalizeTheme, resolveDefaultTheme) stay and are used across Tenant / Auth / User flows; resolveDefaultTheme now hardcodes 'light' with a catalog fallback (no global/env lookup). - AdminSettingsService: buildPageData, sanitization, audit snapshot, apply step and cache payload are all stripped of the three keys. Dead helper applyAppPrimaryColor + normalizePrimaryColor removed. - Cache: SettingCacheService HOT_PATH_KEYS drops the three keys. - API: /settings (GET/PUT) and /settings/public (GET) no longer expose appearance fields; OpenAPI schemas pruned accordingly. - Audit redaction list shortened. - DB: db/init/init.sql seed rows removed. No migration for existing installs — legacy rows stay inert. - i18n + docs: setting.app_theme, setting.app_theme_user, setting.app_primary_color removed from de+en locales; reference and explanation docs updated. - Tests: covering tests for the removed methods pruned; ThemeResolutionTest rewritten for tenant-only semantics. One unrelated PHP-CS-Fixer issue in UserRegistrar.php cleaned up to keep QG-006 green. Workflow: .agents/runs/SETTINGS-APPEARANCE-TENANT-ONLY/ (gitignored). Gates: QG-001..003, QG-006, QG-008 all pass (1985 tests, 28764 assertions). Reviews: code, security, acceptance all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -265,6 +265,10 @@ function appDefaultLocale(): ?string
|
||||
|
||||
/**
|
||||
* Feature flag: allow users to choose their own theme.
|
||||
*
|
||||
* Resolved from the current tenant; defaults to true when no tenant is in
|
||||
* session or the tenant has no explicit value. Appearance is tenant-scoped —
|
||||
* there is no global setting fallback.
|
||||
*/
|
||||
function allowUserTheme(): bool
|
||||
{
|
||||
@@ -273,7 +277,7 @@ function allowUserTheme(): bool
|
||||
return settingToBool((string) $tenantValue, true);
|
||||
}
|
||||
|
||||
return settingToBool(appSetting('app_theme_user'), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,10 @@ function appThemes(): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve default theme from settings, then APP_THEME, then light.
|
||||
* Resolve default theme: tenant value, else hardcoded 'light'.
|
||||
*
|
||||
* Appearance is tenant-scoped — there is no global fallback layer. Pages
|
||||
* without a tenant context (login, error) land on 'light'.
|
||||
*/
|
||||
function appDefaultTheme(): string
|
||||
{
|
||||
@@ -31,13 +34,7 @@ function appDefaultTheme(): string
|
||||
if ($tenantTheme !== '' && isset($themes[$tenantTheme])) {
|
||||
return $tenantTheme;
|
||||
}
|
||||
$setting = appSetting('app_theme');
|
||||
if ($setting !== null && isset($themes[$setting])) {
|
||||
return $setting;
|
||||
}
|
||||
$envTheme = getenv('APP_THEME') ?: 'light';
|
||||
$envTheme = strtolower(trim((string) $envTheme));
|
||||
return isset($themes[$envTheme]) ? $envTheme : 'light';
|
||||
return 'light';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,28 +56,24 @@ function currentTheme(): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve active primary color (tenant override -> app setting).
|
||||
* Resolve active primary color from the current tenant.
|
||||
*
|
||||
* Returns null when no tenant is in session (login / error pages) or when
|
||||
* the tenant has no primary_color set. Callers must be null-safe — there is
|
||||
* no global fallback; appPrimaryCssVars() emits an empty string in that case
|
||||
* so Pico defaults apply.
|
||||
*/
|
||||
function appPrimaryColor(): ?string
|
||||
{
|
||||
// Tenant-scoped branding has precedence over global settings.
|
||||
$tenantColor = $_SESSION['current_tenant']['primary_color'] ?? null;
|
||||
if ($tenantColor !== null) {
|
||||
$tenantColor = strtolower(trim((string) $tenantColor));
|
||||
if (preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $tenantColor)) {
|
||||
return $tenantColor;
|
||||
}
|
||||
}
|
||||
|
||||
$value = appSetting('app_primary_color');
|
||||
if ($value === null) {
|
||||
if ($tenantColor === null) {
|
||||
return null;
|
||||
}
|
||||
$value = strtolower(trim($value));
|
||||
if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
|
||||
$tenantColor = strtolower(trim((string) $tenantColor));
|
||||
if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $tenantColor)) {
|
||||
return null;
|
||||
}
|
||||
return $value;
|
||||
return $tenantColor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user