refactor(support): consolidate remaining ID normalization via toIntIds()

Extends the toIntIds() rollout to service and repository layers:

- Drops a third private duplicate (UserCustomFieldValueService::normalizeTenantIds)
- Collapses the two-line intval+filter pattern inside UserAssignmentService,
  UserAuthorizationPolicy, SsoUserLinkService, DepartmentService, and
  UserCustomFieldValueService
- Replaces inline patterns in 4 repositories (RolePermissionRepository,
  RoleAssignableRoleRepository, UserWriteRepository, DepartmentRepository,
  UserCustomFieldValueRepository, UserCustomFieldValueOptionRepository)
- Simplifies the notifications sanitizeTenantIds trait body

Tenant-area files deliberately untouched per parallel ongoing work on
the tenant-logo refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 19:18:45 +02:00
parent 6c99c040b2
commit 1c779b8eeb
12 changed files with 23 additions and 54 deletions

View File

@@ -409,9 +409,7 @@ class UserAuthorizationPolicy implements AuthorizationPolicyInterface
'can_view_security_artifacts' => $isOwnAccount || $canViewUserAudit,
'can_view_permissions_table' => $this->hasPermission($actorUserId, PermissionService::PERMISSIONS_VIEW),
'is_own_account' => $isOwnAccount,
'allowed_tenant_ids' => is_array($allowedTenantIds)
? array_values(array_unique(array_map('intval', $allowedTenantIds)))
: null,
'allowed_tenant_ids' => is_array($allowedTenantIds) ? toIntIds($allowedTenantIds) : null,
];
return AuthorizationDecision::allow(['capabilities' => $capabilities]);

View File

@@ -377,7 +377,7 @@ class SsoUserLinkService
private function ensureTenantAssignment(int $userId, int $tenantId): void
{
$tenantIds = array_values(array_unique(array_map('intval', $this->userTenantRepository->listTenantIdsByUserId($userId))));
$tenantIds = toIntIds($this->userTenantRepository->listTenantIdsByUserId($userId));
if (!in_array($tenantId, $tenantIds, true)) {
$tenantIds[] = $tenantId;
$this->userAssignmentService->syncTenants($userId, $tenantIds);

View File

@@ -25,7 +25,7 @@ class UserCustomFieldValueService
if (!$canEdit) {
return ['ok' => true, 'errors' => []];
}
$tenantIds = self::normalizeTenantIds($tenantIds);
$tenantIds = toIntIds($tenantIds);
if (!$tenantIds) {
return ['ok' => true, 'errors' => []];
}
@@ -38,7 +38,7 @@ class UserCustomFieldValueService
public function buildDefinitionsByTenant(array $tenantIds, bool $includeInactive = false): array
{
$tenantIds = self::normalizeTenantIds($tenantIds);
$tenantIds = toIntIds($tenantIds);
if (!$tenantIds) {
return [];
}
@@ -83,8 +83,7 @@ class UserCustomFieldValueService
if ($userId <= 0) {
return [];
}
$definitionIds = array_values(array_unique(array_map('intval', $definitionIds)));
$definitionIds = array_values(array_filter($definitionIds, static fn ($id) => $id > 0));
$definitionIds = toIntIds($definitionIds);
if (!$definitionIds) {
return [];
}
@@ -251,7 +250,7 @@ class UserCustomFieldValueService
return ['ok' => false, 'errors' => [t('User not found')]];
}
$tenantIds = self::normalizeTenantIds($tenantIds);
$tenantIds = toIntIds($tenantIds);
// Always clean values for tenants no longer assigned — happens even when !$canEdit,
// because orphaned custom field data should not persist after tenant reassignment.
if (!$this->valueRepository->deleteByUserOutsideTenantIds($userId, $tenantIds)) {
@@ -541,12 +540,6 @@ class UserCustomFieldValueService
return $this->userScopeGateway;
}
private static function normalizeTenantIds(array $tenantIds): array
{
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
return array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
}
private static function normalizeBool($value): ?int
{
if (is_bool($value)) {

View File

@@ -43,8 +43,7 @@ class DepartmentService
public function groupActiveByTenantIds(array $tenantIds): array
{
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
$tenantIds = array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
$tenantIds = toIntIds($tenantIds);
if (!$tenantIds) {
return [];
}

View File

@@ -176,8 +176,7 @@ class UserAssignmentService
public function syncRoles(int $userId, array $roleIds, bool $bumpAuthz = true, int $actorUserId = 0): bool
{
$ids = array_values(array_unique(array_map('intval', $roleIds)));
$ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
$ids = toIntIds($roleIds);
$validIds = $this->directoryGateway->listActiveRoleIds();
if ($validIds) {
$validMap = array_fill_keys($validIds, true);
@@ -201,16 +200,11 @@ class UserAssignmentService
public function syncDepartments(int $userId, array $departmentIds, bool $bumpAuthz = true): bool
{
$ids = array_values(array_unique(array_map('intval', $departmentIds)));
$ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
$ids = toIntIds($departmentIds);
// Important: use the user's assigned tenants directly (no permission bypass),
// otherwise privileged users (e.g. admins) would incorrectly allow departments
// from tenants that were just removed from their assignments.
$userTenantIds = array_values(array_unique(array_map(
'intval',
$this->userTenantRepository->listTenantIdsByUserId($userId)
)));
$userTenantIds = array_values(array_filter($userTenantIds, static fn ($id) => $id > 0));
$userTenantIds = toIntIds($this->userTenantRepository->listTenantIdsByUserId($userId));
if (!$userTenantIds) {
$ids = [];
} else {
@@ -304,14 +298,12 @@ class UserAssignmentService
$collect($item);
}
$ids = array_values(array_unique(array_map('intval', $flat)));
return array_values(array_filter($ids, static fn ($id) => $id > 0));
return toIntIds($flat);
}
public function normalizeTenantIds(array $tenantIds): array
{
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
$ids = array_filter($ids, static fn ($id) => $id > 0);
$ids = toIntIds($tenantIds);
$validIds = $this->directoryGateway->listTenantIds();
if ($validIds) {
$validMap = array_fill_keys($validIds, true);