1
0

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:
2026-04-21 14:04:21 +02:00
parent 6ac685084d
commit fb86c02427
9 changed files with 151 additions and 161 deletions

View File

@@ -3,9 +3,11 @@
namespace MintyPHP\Module\Helpdesk\Repository;
use MintyPHP\DB;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
class DomainSecurityLevelRepository
{
private const TABLE = 'helpdesk_domain_security_levels';
private const ALLOWED_LEVELS = ['niedrig', 'normal', 'hoch', 'kritisch'];
public function findByTenantAndDomainNo(int $tenantId, string $domainNo): ?array
@@ -15,18 +17,16 @@ class DomainSecurityLevelRepository
}
$row = DB::selectOne(
'SELECT * FROM helpdesk_domain_security_levels WHERE tenant_id = ? AND domain_no = ? LIMIT 1',
'SELECT id, tenant_id, domain_no, security_level, note, created_by, updated_by, created_at, updated_at FROM ' . self::TABLE . ' WHERE tenant_id = ? AND domain_no = ? LIMIT 1',
(string) $tenantId,
$domainNo
);
if (!is_array($row) || $row === []) {
return null;
}
return $row;
return RepositoryArrayHelper::unwrap(is_array($row) ? $row : null, self::TABLE);
}
/**
* @api Called from DomainSecurityLevelService
* @param list<string> $domainNos
* @return array<string, string> Map of domain_no => security_level
*/
@@ -37,22 +37,19 @@ class DomainSecurityLevelRepository
}
$placeholders = implode(',', array_fill(0, count($domainNos), '?'));
$params = array_map('strval', $domainNos);
$params[] = (string) $tenantId;
$params = [(string) $tenantId, ...array_map('strval', $domainNos)];
$rows = DB::select(
'SELECT domain_no, security_level FROM helpdesk_domain_security_levels WHERE tenant_id = ? AND domain_no IN (' . $placeholders . ')',
'SELECT domain_no, security_level FROM ' . self::TABLE . ' WHERE tenant_id = ? AND domain_no IN (' . $placeholders . ')',
...$params
);
$result = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$domainNo = (string) ($row['domain_no'] ?? '');
$level = (string) ($row['security_level'] ?? 'normal');
if ($domainNo !== '') {
$result[$domainNo] = in_array($level, self::ALLOWED_LEVELS, true) ? $level : 'normal';
}
foreach (RepositoryArrayHelper::unwrapList($rows, self::TABLE) as $row) {
$domainNo = (string) ($row['domain_no'] ?? '');
$level = (string) ($row['security_level'] ?? 'normal');
if ($domainNo !== '') {
$result[$domainNo] = in_array($level, self::ALLOWED_LEVELS, true) ? $level : 'normal';
}
}
@@ -60,6 +57,7 @@ class DomainSecurityLevelRepository
}
/**
* @api Called from DomainSecurityLevelService
* @param list<string> $domainNos
* @return array<string, array{level: string, note: string}> Map of domain_no => {level, note}
*/
@@ -70,26 +68,23 @@ class DomainSecurityLevelRepository
}
$placeholders = implode(',', array_fill(0, count($domainNos), '?'));
$params = array_map('strval', $domainNos);
$params[] = (string) $tenantId;
$params = [(string) $tenantId, ...array_map('strval', $domainNos)];
$rows = DB::select(
'SELECT domain_no, security_level, note FROM helpdesk_domain_security_levels WHERE tenant_id = ? AND domain_no IN (' . $placeholders . ')',
'SELECT domain_no, security_level, note FROM ' . self::TABLE . ' WHERE tenant_id = ? AND domain_no IN (' . $placeholders . ')',
...$params
);
$result = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$domainNo = (string) ($row['domain_no'] ?? '');
$level = (string) ($row['security_level'] ?? 'normal');
$note = (string) ($row['note'] ?? '');
if ($domainNo !== '') {
$result[$domainNo] = [
'level' => in_array($level, self::ALLOWED_LEVELS, true) ? $level : 'normal',
'note' => $note,
];
}
foreach (RepositoryArrayHelper::unwrapList($rows, self::TABLE) as $row) {
$domainNo = (string) ($row['domain_no'] ?? '');
$level = (string) ($row['security_level'] ?? 'normal');
$note = (string) ($row['note'] ?? '');
if ($domainNo !== '') {
$result[$domainNo] = [
'level' => in_array($level, self::ALLOWED_LEVELS, true) ? $level : 'normal',
'note' => $note,
];
}
}
@@ -99,6 +94,7 @@ class DomainSecurityLevelRepository
/**
* Insert or update security level for a domain.
*
* @api Called from DomainSecurityLevelService
* @return bool True on success
*/
public function upsert(int $tenantId, string $domainNo, string $level, ?int $userId, ?string $note = null): bool
@@ -111,7 +107,7 @@ class DomainSecurityLevelRepository
if ($existing !== null) {
$result = DB::update(
'UPDATE helpdesk_domain_security_levels SET security_level = ?, note = ?, updated_by = ? WHERE tenant_id = ? AND domain_no = ?',
'UPDATE ' . self::TABLE . ' SET security_level = ?, note = ?, updated_by = ? WHERE tenant_id = ? AND domain_no = ?',
$level,
$note,
$userId !== null ? (string) $userId : null,
@@ -123,7 +119,7 @@ class DomainSecurityLevelRepository
}
$result = DB::insert(
'INSERT INTO helpdesk_domain_security_levels (tenant_id, domain_no, security_level, note, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?)',
'INSERT INTO ' . self::TABLE . ' (tenant_id, domain_no, security_level, note, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?)',
(string) $tenantId,
$domainNo,
$level,
@@ -145,7 +141,7 @@ class DomainSecurityLevelRepository
}
$result = DB::update(
'DELETE FROM helpdesk_domain_security_levels WHERE tenant_id = ? AND domain_no = ?',
'DELETE FROM ' . self::TABLE . ' WHERE tenant_id = ? AND domain_no = ?',
(string) $tenantId,
$domainNo
);