refactor(tenants): visibility form requires explicit appearance values
Simplifies the tenant Visibility tab to match the tenant-only appearance
model established by the previous commits:
- primary_color: now required. Removed the "No brand color / Use system
default (no brand accent)" toggle — every tenant carries a concrete
primary color. Legacy NULL rows render the neutral app default (#2fa4a4)
in the color picker and are converted to that explicit hex on save.
- default_theme: the select no longer offers a "Use system default" empty
option. Legacy NULL rows resolve to 'light' at render time; saves always
persist a valid theme via SettingsAppGateway::normalizeTheme().
- allow_user_theme: replaced the tri-state "Use system default (allowed) /
Force allow / Force disallow" select with a single boolean switch
("Users may choose their own theme"). Legacy NULL rows load as checked.
Saves persist 0/1 explicitly.
TenantService: sanitize no longer reads primary_color_use_default or
allow_user_theme_mode; it validates primary_color as a required hex and
treats allow_user_theme as a plain boolean. Both create and update paths
write concrete values only — no more NULL writes for these three fields.
DirectorySettingsGateway gains a normalizeTheme() delegate so TenantService
can route through the same gateway it uses for isAllowedTheme().
Removed now-unused app-color-default-toggle JS component + its runtime
registration + its architecture-test entry. i18n cleanup: "No brand
color", "Use system default (no brand accent)", "When enabled the tenant
renders without a brand accent color.", "Use system default (light)",
"Use system default (allowed)", "Force allow user theme", "Force disallow
user theme", "User theme policy is invalid" all removed. New copy: "Users
may choose their own theme" + helper text, plus a tightened tab blockquote.
Tests: TenantServiceTest validInput() updated to send concrete values;
settingsGateway mock gets normalizeTheme() + isAllowedTheme() defaults.
All 1985 tests pass; PHPStan level 5 clean; QG-006 clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,22 +34,27 @@ $customFieldDefinitions = is_array($customFieldDefinitions ?? null) ? $customFie
|
||||
$readonlyAttr = $isReadOnly ? 'readonly' : '';
|
||||
$disabledAttr = $isReadOnly ? 'disabled' : '';
|
||||
$themes = appThemes();
|
||||
// Primary color is required per tenant — appearance is tenant-scoped and
|
||||
// there is no "no brand color" opt-out. Legacy rows (NULL) are rendered
|
||||
// with the neutral app default; saving converts NULL to that explicit hex.
|
||||
$primaryColor = (string) ($values['primary_color'] ?? '');
|
||||
// No global fallback — appearance is tenant-scoped. The color picker's
|
||||
// "use default" preview uses a neutral hex so the UI is never empty.
|
||||
$defaultPrimaryColor = '#2fa4a4';
|
||||
$useDefaultPrimaryColor = $primaryColor === '';
|
||||
$tenantDefaultTheme = strtolower(trim((string) ($values['default_theme'] ?? '')));
|
||||
if ($tenantDefaultTheme !== '' && !isset($themes[$tenantDefaultTheme])) {
|
||||
$tenantDefaultTheme = '';
|
||||
if ($primaryColor === '' || !preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $primaryColor)) {
|
||||
$primaryColor = '#2fa4a4';
|
||||
}
|
||||
$rawAllowUserThemeMode = $values['allow_user_theme_mode'] ?? ($values['allow_user_theme'] ?? null);
|
||||
if ($rawAllowUserThemeMode === null || $rawAllowUserThemeMode === '') {
|
||||
$tenantAllowUserThemeMode = '';
|
||||
} elseif (in_array(strtolower((string) $rawAllowUserThemeMode), ['1', 'true', 'yes', 'on'], true)) {
|
||||
$tenantAllowUserThemeMode = '1';
|
||||
// Resolve tenant's default theme against the catalog; fall back to 'light'
|
||||
// for legacy rows or unknown values. The select never renders an empty
|
||||
// option — appearance is tenant-scoped, no global setting to inherit.
|
||||
$tenantDefaultTheme = strtolower(trim((string) ($values['default_theme'] ?? '')));
|
||||
if ($tenantDefaultTheme === '' || !isset($themes[$tenantDefaultTheme])) {
|
||||
$tenantDefaultTheme = isset($themes['light']) ? 'light' : (array_key_first($themes) ?: 'light');
|
||||
}
|
||||
// allow_user_theme: boolean — true when set or unset (legacy NULL), false
|
||||
// only when explicitly disabled. Eliminates the old tri-state select.
|
||||
$rawAllowUserTheme = $values['allow_user_theme'] ?? null;
|
||||
if ($rawAllowUserTheme === null || $rawAllowUserTheme === '') {
|
||||
$tenantAllowUserTheme = true;
|
||||
} else {
|
||||
$tenantAllowUserThemeMode = '0';
|
||||
$tenantAllowUserTheme = in_array(strtolower((string) $rawAllowUserTheme), ['1', 'true', 'yes', 'on'], true);
|
||||
}
|
||||
$microsoftEnabledRaw = $values['microsoft_enabled'] ?? false;
|
||||
$microsoftEnabled = in_array(strtolower(trim((string) $microsoftEnabledRaw)), ['1', 'true', 'yes', 'on'], true);
|
||||
@@ -933,45 +938,25 @@ $openOverrideCard = $detailsOpenAll;
|
||||
|
||||
<div data-tab-panel="visibility">
|
||||
<blockquote data-variant="info">
|
||||
<?php e(t('Appearance is controlled per tenant. If a field is left at the system default, the app renders in light theme without a brand accent.')); ?>
|
||||
<?php e(t('Appearance is controlled per tenant. Every tenant carries its own primary color and default theme.')); ?>
|
||||
</blockquote>
|
||||
<div class="grid">
|
||||
<fieldset>
|
||||
<legend><small><?php e(t('Primary color')); ?></small></legend>
|
||||
<label for="primary_color">
|
||||
<input type="color" name="primary_color" id="primary_color"
|
||||
value="<?php e($primaryColor !== '' ? $primaryColor : $defaultPrimaryColor); ?>"
|
||||
<?php e($readonlyAttr); ?> />
|
||||
<div>
|
||||
<?php e(t('Choose color')); ?>
|
||||
</div>
|
||||
</label>
|
||||
<small class="muted"><?php e(t('The primary color affects buttons and accent elements across the app.')); ?></small>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<small>
|
||||
<?php e(t('No brand color')); ?>
|
||||
</small>
|
||||
</legend>
|
||||
<label class="app-field">
|
||||
<input type="hidden" name="primary_color_use_default" value="0">
|
||||
<input type="checkbox" role="switch" name="primary_color_use_default" value="1"
|
||||
data-color-default-toggle
|
||||
data-color-target="#primary_color"
|
||||
data-color-default="<?php e($defaultPrimaryColor); ?>"
|
||||
<?php e($useDefaultPrimaryColor ? 'checked' : ''); ?>
|
||||
<?php e($readonlyAttr); ?>>
|
||||
<span><?php e(t('Use system default (no brand accent)')); ?></span>
|
||||
</label>
|
||||
<small class="muted"><?php e(t('When enabled the tenant renders without a brand accent color.')); ?></small>
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend><small><?php e(t('Primary color')); ?></small></legend>
|
||||
<label for="primary_color">
|
||||
<input type="color" name="primary_color" id="primary_color"
|
||||
value="<?php e($primaryColor); ?>"
|
||||
required
|
||||
<?php e($readonlyAttr); ?> />
|
||||
<div>
|
||||
<?php e(t('Choose color')); ?>
|
||||
</div>
|
||||
</label>
|
||||
<small class="muted"><?php e(t('The primary color affects buttons and accent elements across the app.')); ?></small>
|
||||
</fieldset>
|
||||
<div class="grid">
|
||||
<fieldset>
|
||||
<legend><small><?php e(t('Default theme')); ?></small></legend>
|
||||
<select name="default_theme" <?php e($disabledAttr); ?>>
|
||||
<option value=""><?php e(t('Use system default (light)')); ?></option>
|
||||
<?php foreach ($themes as $themeKey => $themeLabel): ?>
|
||||
<option value="<?php e($themeKey); ?>" <?php e($tenantDefaultTheme === $themeKey ? 'selected' : ''); ?>>
|
||||
<?php e(t($themeLabel)); ?>
|
||||
@@ -982,18 +967,14 @@ $openOverrideCard = $detailsOpenAll;
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend><small><?php e(t('User theme policy')); ?></small></legend>
|
||||
<select name="allow_user_theme_mode" <?php e($disabledAttr); ?>>
|
||||
<option value="" <?php e($tenantAllowUserThemeMode === '' ? 'selected' : ''); ?>>
|
||||
<?php e(t('Use system default (allowed)')); ?>
|
||||
</option>
|
||||
<option value="1" <?php e($tenantAllowUserThemeMode === '1' ? 'selected' : ''); ?>>
|
||||
<?php e(t('Force allow user theme')); ?>
|
||||
</option>
|
||||
<option value="0" <?php e($tenantAllowUserThemeMode === '0' ? 'selected' : ''); ?>>
|
||||
<?php e(t('Force disallow user theme')); ?>
|
||||
</option>
|
||||
</select>
|
||||
<small class="muted"><?php e(t('Controls whether users in this tenant can choose their own theme.')); ?></small>
|
||||
<label class="app-field">
|
||||
<input type="hidden" name="allow_user_theme" value="0">
|
||||
<input type="checkbox" role="switch" name="allow_user_theme" value="1"
|
||||
<?php e($tenantAllowUserTheme ? 'checked' : ''); ?>
|
||||
<?php e($readonlyAttr); ?>>
|
||||
<span><?php e(t('Users may choose their own theme')); ?></span>
|
||||
</label>
|
||||
<small class="muted"><?php e(t('Controls whether users in this tenant can override the tenant default with a personal theme preference.')); ?></small>
|
||||
</fieldset>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user