refactor(settings): split admin/settings into hub + per-section subpages

Splits the 755-line monolithic settings page into a tile-based landing hub
and six focused subpages (general, security, email, api, sso, branding),
each with its own form, CSRF scope and POST handler. Each subpage offers
Save / Save & close buttons plus a Cancel/back link to the hub.

Backend (AdminSettingsService, gateways, policies, DB schema) unchanged.
A new settingsSectionMergePost() helper overlays section POSTs onto the
current DB values so partial saves don't wipe unrelated fields (the
service defaults missing keys to 0/empty).

Sub-action files (logo/favicon/tokens/lifecycle) redirect to the matching
subpage, and architecture contracts now check the subpage files instead
of the removed monolithic index.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 22:43:12 +02:00
parent 50851c0e9a
commit 69739c90d7
30 changed files with 4233 additions and 3421 deletions

View File

@@ -10,10 +10,22 @@ class AuthzAdminSettingsContractTest extends TestCase
public function testAdminSettingsActionsUseCentralPolicies(): void
{
// Landing hub: VIEW only (no POST handler).
$indexContent = $this->readProjectFile('pages/admin/settings/index().php');
$this->assertStringContainsString('AuthorizationService::class', $indexContent);
$this->assertStringContainsString('SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_VIEW', $indexContent);
$this->assertStringContainsString('SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE', $indexContent);
// Subpages that expose a settings form: VIEW + UPDATE.
foreach (['general', 'security', 'email', 'api', 'sso'] as $section) {
$content = $this->readProjectFile("pages/admin/settings/{$section}().php");
$this->assertStringContainsString('AuthorizationService::class', $content, $section);
$this->assertStringContainsString('SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_VIEW', $content, $section);
$this->assertStringContainsString('SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE', $content, $section);
}
// Branding landing: VIEW only (uploads post to separate action files).
$brandingIndex = $this->readProjectFile('pages/admin/settings/branding().php');
$this->assertStringContainsString('SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_VIEW', $brandingIndex);
$logoUpload = $this->readProjectFile('pages/admin/settings/logo().php');
$this->assertStringContainsString('AuthorizationService::class', $logoUpload);

View File

@@ -56,9 +56,14 @@ class AuthzUiTemplateContractTest extends TestCase
public function testAdminSettingsTemplateUsesServerCapabilities(): void
{
$templateContent = $this->readProjectFile('pages/admin/settings/index(default).phtml');
$this->assertStringNotContainsString("can('settings.update')", $templateContent);
$this->assertStringContainsString('$canUpdateSettings', $templateContent);
// Settings was split into a landing hub + per-section subpages.
// The form-carrying subpages must use the server-side $canUpdateSettings
// capability instead of the legacy can() helper.
foreach (['general', 'security', 'email', 'api', 'sso', 'branding'] as $section) {
$templateContent = $this->readProjectFile("pages/admin/settings/{$section}(default).phtml");
$this->assertStringNotContainsString("can('settings.update')", $templateContent, $section);
$this->assertStringContainsString('$canUpdateSettings', $templateContent, $section);
}
}
public function testAdminUserEditTemplateUsesServerCapabilities(): void

View File

@@ -17,7 +17,11 @@ final class DetailActionPolicyContractFiles
'pages/admin/departments/edit(default).phtml',
'pages/admin/roles/edit(default).phtml',
'pages/admin/permissions/edit(default).phtml',
'pages/admin/settings/index(default).phtml',
// Settings subpages that carry danger actions (and therefore need
// the data-detail-confirm-message contract) — general/email/sso/branding
// have no destructive buttons and are deliberately excluded.
'pages/admin/settings/security(default).phtml',
'pages/admin/settings/api(default).phtml',
'templates/partials/app-details-titlebar.phtml',
'templates/partials/app-details-aside-actions.phtml',
];

View File

@@ -16,7 +16,11 @@ final class DetailPageContractFiles
'pages/admin/roles/_form.phtml',
'pages/admin/permissions/_form.phtml',
'pages/admin/scheduled-jobs/edit(default).phtml',
'pages/admin/settings/index(default).phtml',
'pages/admin/settings/general(default).phtml',
'pages/admin/settings/security(default).phtml',
'pages/admin/settings/email(default).phtml',
'pages/admin/settings/api(default).phtml',
'pages/admin/settings/sso(default).phtml',
];
}
}

View File

@@ -45,7 +45,6 @@ class FrontendRuntimeHostContractTest extends TestCase
'pages/admin/departments/_form.phtml',
'pages/admin/permissions/_form.phtml',
'pages/admin/roles/_form.phtml',
'pages/admin/settings/index(default).phtml',
'pages/admin/stats/index(default).phtml',
'pages/admin/tenants/_form.phtml',
'pages/admin/users/_form.phtml',