fix(helpdesk): repair security-level persistence, enrichment, and list UX
Saving security levels appeared broken in both the domain detail view and the domains grid. Root causes: - Repository never unwrapped MintyPHP DB rows (nested by table name), so reads always returned defaults even though writes succeeded. - Batch queries bound tenant_id last while SQL expected it first, so list/detail enrichment for the grid matched nothing. - Detail page AJAX submit looked up CSRF via a non-existent data attribute and dropped the note field entirely. - Endpoint used a hand-rolled CSRF check instead of Session::checkCsrfToken(). Fixes: - DomainSecurityLevelRepository: use RepositoryArrayHelper::unwrap(List), swap param order in listLevelsByDomainNos / listDetailsByDomainNos, centralize table name in a const. - security-level-data endpoint: canonical Session::checkCsrfToken(), unified JSON/PRG error path with proper HTTP status codes and flash. - Detail page: drop broken AJAX hijack, rely on native form POST + PRG (GR-CORE-012). - Grid list: refetch via gridRef.grid.forceRender() after dialog save instead of DOM-only mutation, so Grid.js internal state stays in sync. - Add dedicated Note column next to the badge with ellipsis + title tooltip. - Replace console.* in the dialog with showAsyncFlash for user-visible errors; add i18n keys (de/en) for all error messages. - Drop unused postAction import and TENANT_ID_OTHER test constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,8 @@ require templatePath('partials/app-list-filters.phtml');
|
||||
'administration' => t('Administration'),
|
||||
'contractType' => t('Contract type'),
|
||||
'securityLevel' => t('Security level'),
|
||||
'note' => t('Note'),
|
||||
'saveError' => t('Save failed'),
|
||||
],
|
||||
]); ?></script>
|
||||
<script type="module" src="<?php e(assetVersion('modules/helpdesk/js/pages/helpdesk-domains-index.js')); ?>"></script>
|
||||
|
||||
@@ -15,85 +15,76 @@ $request = requestInput();
|
||||
$session = app(SessionStoreInterface::class)->all();
|
||||
$tenantId = (int) ($session['current_tenant']['id'] ?? 0);
|
||||
$userId = (int) ($session['user']['id'] ?? 0);
|
||||
$service = app(DomainSecurityLevelService::class);
|
||||
$isPost = $request->isMethod('POST');
|
||||
$wantsJson = $request->wantsJson();
|
||||
|
||||
$domainNo = trim((string) ($isPost
|
||||
? $request->body('domain_no', '')
|
||||
: $request->query('domain_no', '')));
|
||||
|
||||
$redirectUrl = $domainNo !== ''
|
||||
? 'helpdesk/domain/' . rawurlencode($domainNo)
|
||||
: 'helpdesk/domains';
|
||||
|
||||
$fail = static function (string $error, int $status = 400) use ($wantsJson, $redirectUrl): void {
|
||||
if ($wantsJson) {
|
||||
http_response_code($status);
|
||||
Router::json(['ok' => false, 'error' => $error]);
|
||||
return;
|
||||
}
|
||||
Flash::error(t($error), 'helpdesk-domains', 'security_level_error');
|
||||
Router::redirect($redirectUrl);
|
||||
};
|
||||
|
||||
if ($tenantId <= 0) {
|
||||
Router::json(['ok' => false, 'error' => 'No tenant context']);
|
||||
|
||||
$fail('No tenant context', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$domainNo = trim((string) ($request->body('domain_no', '') ?: $request->query('domain_no', '')));
|
||||
|
||||
if ($domainNo === '') {
|
||||
Router::json(['ok' => false, 'error' => 'Missing domain number']);
|
||||
|
||||
$fail('Missing domain number', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$service = app(DomainSecurityLevelService::class);
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
// CSRF check: Compare token from request body with session token
|
||||
$csrfKey = Session::$csrfSessionKey;
|
||||
$csrfSession = (string) ($session[$csrfKey] ?? '');
|
||||
$csrfRequest = (string) $request->body($csrfKey, '');
|
||||
|
||||
if ($csrfRequest === '' || $csrfRequest !== $csrfSession) {
|
||||
// For AJAX requests, return JSON error
|
||||
if ($request->wantsJson()) {
|
||||
Router::json(['ok' => false, 'error' => 'Invalid CSRF token']);
|
||||
return;
|
||||
}
|
||||
// For full-page POST, redirect with flash error
|
||||
Flash::error(t('Invalid CSRF token'), 'helpdesk-domains', 'csrf');
|
||||
Router::redirect('helpdesk/domain/' . rawurlencode($domainNo));
|
||||
return;
|
||||
}
|
||||
|
||||
$level = trim((string) $request->body('security_level', ''));
|
||||
|
||||
if ($level === '') {
|
||||
Router::json(['ok' => false, 'error' => 'Missing security level']);
|
||||
return;
|
||||
}
|
||||
|
||||
$allowed = DomainSecurityLevelService::getAllowedLevels();
|
||||
if (!in_array($level, $allowed, true)) {
|
||||
Router::json(['ok' => false, 'error' => 'Invalid security level']);
|
||||
return;
|
||||
}
|
||||
|
||||
$note = trim((string) $request->body('note', ''));
|
||||
|
||||
$success = $service->setLevel($tenantId, $domainNo, $level, $userId, $note !== '' ? $note : null);
|
||||
|
||||
if (!$success) {
|
||||
Router::json(['ok' => false, 'error' => 'Failed to save security level']);
|
||||
return;
|
||||
}
|
||||
|
||||
// For AJAX requests, return JSON
|
||||
if ($request->wantsJson()) {
|
||||
Router::json([
|
||||
'ok' => true,
|
||||
'level' => $level,
|
||||
'badge_variant' => DomainSecurityLevelService::getBadgeVariant($level),
|
||||
'note' => $note,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// For full-page POST, redirect with flash success
|
||||
Flash::success(t('Security level updated'), 'helpdesk-domains', 'security_level_saved');
|
||||
Router::redirect('helpdesk/domain/' . rawurlencode($domainNo));
|
||||
if (!$isPost) {
|
||||
$details = $service->getDetails($tenantId, $domainNo);
|
||||
Router::json([
|
||||
'ok' => true,
|
||||
'level' => $details['level'],
|
||||
'badge_variant' => DomainSecurityLevelService::getBadgeVariant($details['level']),
|
||||
'note' => $details['note'],
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$details = $service->getDetails($tenantId, $domainNo);
|
||||
if (!Session::checkCsrfToken()) {
|
||||
$fail('Invalid CSRF token', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
Router::json([
|
||||
'ok' => true,
|
||||
'level' => $details['level'],
|
||||
'badge_variant' => DomainSecurityLevelService::getBadgeVariant($details['level']),
|
||||
'note' => $details['note'],
|
||||
]);
|
||||
$level = trim((string) $request->body('security_level', ''));
|
||||
if (!in_array($level, DomainSecurityLevelService::getAllowedLevels(), true)) {
|
||||
$fail('Invalid security level', 422);
|
||||
return;
|
||||
}
|
||||
|
||||
$note = trim((string) $request->body('note', ''));
|
||||
|
||||
if (!$service->setLevel($tenantId, $domainNo, $level, $userId > 0 ? $userId : null, $note !== '' ? $note : null)) {
|
||||
$fail('Failed to save security level', 500);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($wantsJson) {
|
||||
Router::json([
|
||||
'ok' => true,
|
||||
'level' => $level,
|
||||
'badge_variant' => DomainSecurityLevelService::getBadgeVariant($level),
|
||||
'note' => $note,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Flash::success(t('Security level updated'), 'helpdesk-domains', 'security_level_saved');
|
||||
Router::redirect($redirectUrl);
|
||||
|
||||
Reference in New Issue
Block a user