all(); Guard::requireLogin(); $request = requestInput(); $currentUserId = (int) ($session['user']['id'] ?? 0); $authorizationService = app(\MintyPHP\Service\Access\AuthorizationService::class); $viewDecision = $authorizationService->authorize(SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_VIEW, [ 'actor_user_id' => $currentUserId, ]); if (!$viewDecision->isAllowed()) { Guard::deny(); } $viewCapabilities = $viewDecision->attribute('capabilities', []); $canUpdateSettings = is_array($viewCapabilities) ? (bool) ($viewCapabilities['can_update_settings'] ?? false) : false; $adminSettingsService = app(AdminSettingsService::class); $pageData = $adminSettingsService->buildPageData(); $values = is_array($pageData['values'] ?? null) ? $pageData['values'] : []; $settings = is_array($pageData['settings'] ?? null) ? $pageData['settings'] : []; if ($request->isMethod('POST') && !actionRequireCsrf('admin/settings/user-lifecycle', 'admin/settings/user-lifecycle', 'csrf_expired')) { return; } if ($request->isMethod('POST')) { $updateDecision = $authorizationService->authorize(SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE, [ 'actor_user_id' => $currentUserId, ]); if (!$updateDecision->isAllowed()) { Guard::deny(); } $sectionKeys = [ 'user_inactivity_deactivate_days', 'user_inactivity_delete_days', ]; $mergedPost = settingsSectionMergePost($values, $request->bodyAll(), $sectionKeys); $updateResult = $adminSettingsService->updateFromAdmin($mergedPost); $errorBag = formErrors(); foreach ((array) ($updateResult['errors'] ?? []) as $error) { if (is_array($error)) { $message = trim((string) ($error['message'] ?? '')); $field = trim((string) ($error['field'] ?? 'input')); if ($message !== '') { $errorBag->add($field !== '' ? $field : 'input', $message); } continue; } $message = trim((string) $error); if ($message !== '') { $errorBag->addGlobal($message); } } if ($errorBag->hasAny()) { flashFormErrors($errorBag, 'admin/settings/user-lifecycle', 'settings_update_error'); Router::redirect('admin/settings/user-lifecycle'); } $redirectTarget = ((string) $request->body('action', 'save') === 'save_close') ? 'admin/settings' : 'admin/settings/user-lifecycle'; Flash::success('Settings updated', $redirectTarget, 'settings_updated'); Router::redirect($redirectTarget); } // ── KPI cockpit data (server-rendered, no async) ───────────────────── $deactivateDays = (int) ($values['user_inactivity_deactivate_days'] ?? 0); $deleteDays = (int) ($values['user_inactivity_delete_days'] ?? 0); $dashboardService = app(UserLifecyclePolicyDashboardService::class); $auditDashboard = app(UserLifecycleAuditDashboardInterface::class); $auditDashboardActive = !($auditDashboard instanceof NullUserLifecycleAuditDashboard); $lastRun = $auditDashboard->lastRun(); $summary = $auditDashboard->summaryByAction(30); $pendingCount = $dashboardService->pendingDeletionCount($deactivateDays, $deleteDays, 7); $lastRunAt = $lastRun !== null ? (string) ($lastRun['created_at'] ?? '') : null; $lastRunStatus = $lastRun !== null ? (string) ($lastRun['status'] ?? '') : null; if ($lastRunAt !== null && $lastRunAt === '') { $lastRunAt = null; } // Build relative-time + status suffix for the Run-Now
summary line and the KPI tile. $lastRunRelative = '—'; $lastRunStatusLabel = ''; if ($lastRunAt !== null) { try { $createdUtc = (new \DateTimeImmutable($lastRunAt, new \DateTimeZone('UTC')))->getTimestamp(); $diffDays = (int) floor((time() - $createdUtc) / 86400); if ($diffDays <= 0) { $lastRunRelative = t('today'); } elseif ($diffDays === 1) { $lastRunRelative = t('yesterday'); } else { $lastRunRelative = sprintf(t('%d days ago'), $diffDays); } } catch (\Throwable) { $lastRunRelative = '—'; } if ($lastRunStatus !== null && $lastRunStatus !== '') { // Status enum tokens are translated via existing 'Success'/'Failed'/'Skipped' keys. $statusKey = ucfirst(strtolower($lastRunStatus)); $lastRunStatusLabel = t($statusKey); } } $lastRunTooltip = $lastRunStatusLabel !== '' ? $lastRunStatusLabel : t('No runs yet'); $pendingDisabled = ($deactivateDays <= 0 || $deleteDays <= 0); /** @var array> $kpiTiles */ $kpiTiles = [ [ 'label' => t('Last run'), 'count' => $lastRunAt !== null ? $lastRunRelative : '—', 'icon' => 'bi bi-clock-history', 'iconTone' => 'blue', 'href' => 'admin/settings/user-lifecycle', 'tooltip' => $lastRunTooltip, ], [ 'label' => t('Deactivated'), 'count' => $auditDashboardActive ? (string) ((int) ($summary['deactivate'] ?? 0)) : '—', 'icon' => 'bi bi-person-dash', 'iconTone' => 'amber', 'href' => 'admin/settings/user-lifecycle', 'tooltip' => t('Last 30 days'), ], [ 'label' => t('Deleted'), 'count' => $auditDashboardActive ? (string) ((int) ($summary['delete'] ?? 0)) : '—', 'icon' => 'bi bi-person-x', 'iconTone' => 'red', 'href' => 'admin/settings/user-lifecycle', 'tooltip' => t('Last 30 days'), ], [ 'label' => t('Pending deletion'), 'count' => $pendingDisabled ? '—' : (string) $pendingCount, 'icon' => 'bi bi-exclamation-triangle', 'iconTone' => 'orange', 'href' => 'admin/users', 'tooltip' => t('Next 7 days'), ], ]; // ── Aside quick actions ────────────────────────────────────────────── $canViewDocs = $authorizationService ->authorize(OperationsAuthorizationPolicy::ABILITY_ADMIN_DOCS_VIEW, ['actor_user_id' => $currentUserId]) ->isAllowed(); /** @var array> $asideActions */ $asideActions = []; if ($canUpdateSettings) { $asideActions[] = [ 'type' => 'form', 'label' => t('Run policy now'), 'action' => 'admin/settings/run-user-lifecycle', 'method' => 'POST', 'class' => 'secondary outline', 'tone' => 'danger', 'confirm' => t('Run user lifecycle now?'), 'detailActionKind' => 'danger', ]; } if ($canUpdateSettings && $auditDashboardActive) { $asideActions[] = [ 'type' => 'form', 'label' => t('Purge logs'), 'action' => 'admin/settings/user-lifecycle/audit-purge', 'method' => 'POST', 'class' => 'secondary outline', 'tone' => 'danger', 'confirm' => t('Purge old user lifecycle audit log entries?'), 'detailActionKind' => 'purge', ]; } if ($canViewDocs) { $asideActions[] = [ 'type' => 'link', 'label' => t('Policy reference'), 'href' => 'admin/docs/reference-benutzer-lifecycle-policy', 'class' => 'secondary outline', ]; } Buffer::set('title', t('User lifecycle settings')); $breadcrumbs = [ ['label' => t('Home'), 'path' => 'admin'], ['label' => t('Settings'), 'path' => 'admin/settings'], ['label' => t('User lifecycle')], ];