all(); Guard::requireLogin(); $request = requestInput(); $returnTarget = requestResolveReturnTarget(); $closeTarget = requestResolveReturnTarget('admin/users'); $userAccountService = app(\MintyPHP\Service\User\UserAccountService::class); $userAssignmentService = app(\MintyPHP\Service\User\UserAssignmentService::class); $userTenantRepository = app(\MintyPHP\Repository\Tenant\UserTenantRepository::class); $userRoleRepository = app(\MintyPHP\Repository\Access\UserRoleRepository::class); $userDepartmentRepository = app(\MintyPHP\Repository\Org\UserDepartmentRepository::class); $userPasswordPolicyService = app(\MintyPHP\Service\User\UserPasswordPolicyService::class); $userCustomFieldValueService = app(UserCustomFieldValueService::class); $authorizationService = app(\MintyPHP\Service\Access\AuthorizationService::class); $rolePermissionRepository = app(\MintyPHP\Repository\Access\RolePermissionRepository::class); $authService = app(\MintyPHP\Service\Auth\AuthService::class); $apiTokenService = app(\MintyPHP\Service\Auth\ApiTokenService::class); $passwordResetRepository = app(PasswordResetRepository::class); $rememberTokenRepository = app(RememberTokenRepository::class); $tenantService = app(\MintyPHP\Service\Tenant\TenantService::class); $roleService = app(\MintyPHP\Service\Access\RoleService::class); $departmentService = app(\MintyPHP\Service\Org\DepartmentService::class); $passwordMinLength = $userPasswordPolicyService->minLength(); $passwordHints = $userPasswordPolicyService->hints(); $currentUserId = (int) ($session['user']['id'] ?? 0); $uuid = trim((string) ($id ?? '')); $editTarget = requestPathWithReturnTarget("admin/users/edit/{$uuid}", $returnTarget); $user = $uuid !== '' ? $userAccountService->findByUuid($uuid) : null; if (!$user) { Flash::error('User not found', $closeTarget, 'user_not_found'); Router::redirect($closeTarget); } $userId = (int) ($user['id'] ?? 0); $contextDecision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_EDIT_CONTEXT, [ 'actor_user_id' => $currentUserId, 'target_user_id' => $userId, ]); if (!$contextDecision->isAllowed()) { Router::redirect('error/forbidden'); return; } $capabilities = $contextDecision->attribute('capabilities', []); if (!is_array($capabilities)) { $capabilities = []; } $canViewUsers = (bool) ($capabilities['can_view_users'] ?? false); $canViewPage = (bool) ($capabilities['can_view_page'] ?? false); $canEditUser = (bool) ($capabilities['can_edit_user'] ?? false); $canEditAssignments = (bool) ($capabilities['can_edit_assignments'] ?? false); $canManageTenants = (bool) ($capabilities['can_manage_tenants'] ?? false); $canEditCustomFieldValues = (bool) ($capabilities['can_edit_custom_field_values'] ?? false); $canManageApiTokens = (bool) ($capabilities['can_manage_api_tokens'] ?? false); $canViewAddressBook = (bool) ($capabilities['can_view_address_book'] ?? false); $canViewUserMeta = (bool) ($capabilities['can_view_user_meta'] ?? false); $canViewUserAudit = (bool) ($capabilities['can_view_user_audit'] ?? false); $canAccessPdf = (bool) ($capabilities['can_access_pdf'] ?? false); $canDeleteUser = (bool) ($capabilities['can_delete_user'] ?? false); $canViewSecurityArtifacts = (bool) ($capabilities['can_view_security_artifacts'] ?? false); $canViewPermissionsTable = (bool) ($capabilities['can_view_permissions_table'] ?? false); $isOwnAccount = (bool) ($capabilities['is_own_account'] ?? false); $allowedTenantIdsRaw = $capabilities['allowed_tenant_ids'] ?? null; $allowedTenantIds = is_array($allowedTenantIdsRaw) ? array_values(array_unique(array_filter(array_map('intval', $allowedTenantIdsRaw), static fn (int $tenantId): bool => $tenantId > 0))) : null; if (!$canViewPage) { Router::redirect('error/forbidden'); return; } if ($canViewUserMeta || $canViewUserAudit) { app(\MintyPHP\Service\Audit\AuditMetadataEnricher::class)->enrich($user, ['created_by', 'modified_by', 'active_changed_by']); } $errorBag = formErrors(); $errors = []; $form = $user; $scopeGateway = app(\MintyPHP\Service\Tenant\TenantScopeService::class); $strictTenantScope = $scopeGateway->isStrict(); $tenants = $tenantService->list(); if (is_array($allowedTenantIds)) { $tenants = array_values(array_filter($tenants, static function (array $tenant) use ($allowedTenantIds): bool { $tenantId = (int) ($tenant['id'] ?? 0); return $tenantId > 0 && in_array($tenantId, $allowedTenantIds, true); })); } elseif (!$canManageTenants && $strictTenantScope) { $tenants = []; } $selectedTenantIds = $userTenantRepository->listTenantIdsByUserId($userId); if (is_array($allowedTenantIds)) { $selectedTenantIds = array_values(array_intersect($selectedTenantIds, $allowedTenantIds)); } $primaryTenantId = (int) ($user['primary_tenant_id'] ?? 0); if (!$primaryTenantId && count($selectedTenantIds) === 1) { $primaryTenantId = (int) $selectedTenantIds[0]; $user['primary_tenant_id'] = $primaryTenantId; } $roles = $roleService->listActive(); $selectedRoleIds = $userRoleRepository->listRoleIdsByUserId($userId); $permissionRows = $canViewPermissionsTable ? $rolePermissionRepository->listPermissionsWithRolesByRoleIds($selectedRoleIds) : []; $selectedDepartmentIds = $userDepartmentRepository->listDepartmentIdsByUserId($userId); $departmentOptionsByTenant = $departmentService->groupActiveByTenantIds($selectedTenantIds); $customFieldDefinitionsByTenant = $userCustomFieldValueService->buildDefinitionsByTenant($selectedTenantIds); $customFieldDefinitionIds = []; foreach ($customFieldDefinitionsByTenant as $tenantDefinitions) { foreach ($tenantDefinitions as $definition) { $definitionId = (int) ($definition['id'] ?? 0); if ($definitionId > 0) { $customFieldDefinitionIds[] = $definitionId; } } } $customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds); $customFieldPostedValues = [ 'custom_field_values' => [], 'custom_field_values_multi' => [], ]; $passwordResets = []; $rememberTokens = []; $apiTokens = []; if ($canViewSecurityArtifacts) { $passwordResets = $passwordResetRepository->listByUserId($userId, 25); $rememberTokens = $rememberTokenRepository->listByUserId($userId, 25); $apiTokens = $apiTokenService->listForUser($userId); } $canManageApiTokens = $canManageApiTokens && $canViewSecurityArtifacts; $showApiTokens = $canViewSecurityArtifacts; if ($request->isMethod('POST') && !Session::checkCsrfToken()) { Flash::error(t('Form expired, please try again'), $editTarget, 'csrf_expired'); Router::redirect($editTarget); return; } if ($request->hasBody('email')) { $post = $request->bodyAll(); $submitDecision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_EDIT_SUBMIT, [ 'actor_user_id' => $currentUserId, 'target_user_id' => $userId, 'input' => $post, ]); if (!$submitDecision->isAllowed()) { Router::redirect('error/forbidden'); return; } $submitCapabilities = $submitDecision->attribute('capabilities', []); if (!is_array($submitCapabilities) || !isset($submitCapabilities['can_edit_user']) || !$submitCapabilities['can_edit_user']) { Router::redirect('error/forbidden'); return; } $canEditUser = (bool) $submitCapabilities['can_edit_user']; $canEditAssignments = (bool) ($submitCapabilities['can_edit_assignments'] ?? $canEditAssignments); $canEditCustomFieldValues = (bool) ($submitCapabilities['can_edit_custom_field_values'] ?? $canEditCustomFieldValues); $result = $userAccountService->updateFromAdmin($userId, $post, $currentUserId); $form = $result['form'] ?? $form; $errorBag->merge($result['errors'] ?? []); $tenantIds = $userAssignmentService->normalizeIdInput($post['tenant_ids'] ?? []); $existingTenantIds = $userTenantRepository->listTenantIdsByUserId($userId); $tenantIdsForSync = $tenantIds; if ($canEditAssignments && is_array($allowedTenantIds)) { $tenantIds = array_values(array_intersect($tenantIds, $allowedTenantIds)); $tenantIdsForSync = $scopeGateway->mergeTenantIdsPreservingOutOfScope( $tenantIds, $existingTenantIds, $allowedTenantIds ); } elseif ($canEditAssignments && !$canManageTenants && $strictTenantScope) { $tenantIds = []; $tenantIdsForSync = $existingTenantIds; } if (!$canEditAssignments) { $tenantIds = $userTenantRepository->listTenantIdsByUserId($userId); if (is_array($allowedTenantIds)) { $tenantIds = array_values(array_intersect($tenantIds, $allowedTenantIds)); } $tenantIdsForSync = $tenantIds; } $selectedTenantIds = $tenantIds; $primaryTenantId = (int) ($post['primary_tenant_id'] ?? 0); $selectedRoleIds = $userAssignmentService->normalizeIdInput($post['role_ids'] ?? []); $selectedDepartmentIds = $userAssignmentService->normalizeIdInput($post['department_ids'] ?? []); $departmentOptionsByTenant = $departmentService->groupActiveByTenantIds($selectedTenantIds); $customFieldDefinitionsByTenant = $userCustomFieldValueService->buildDefinitionsByTenant($selectedTenantIds); $customFieldDefinitionIds = []; foreach ($customFieldDefinitionsByTenant as $tenantDefinitions) { foreach ($tenantDefinitions as $definition) { $definitionId = (int) ($definition['id'] ?? 0); if ($definitionId > 0) { $customFieldDefinitionIds[] = $definitionId; } } } $customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds); $customFieldPostedValues = [ 'custom_field_values' => is_array($post['custom_field_values'] ?? null) ? $post['custom_field_values'] : [], 'custom_field_values_multi' => is_array($post['custom_field_values_multi'] ?? null) ? $post['custom_field_values_multi'] : [], ]; if ($result['ok'] ?? false) { if ($canEditAssignments) { $assignmentsSynced = $userAssignmentService->syncAllAssignments( $userId, $tenantIdsForSync, $selectedRoleIds, $selectedDepartmentIds ); if (!$assignmentsSynced) { $errorBag->addGlobal(t('User assignments can not be updated')); } if ($currentUserId === $userId && !$errorBag->hasAny()) { $authService->loadTenantDataIntoSession($userId); } } $customFieldSyncResult = $userCustomFieldValueService->syncForUser( $userId, $selectedTenantIds, $post, $canEditCustomFieldValues ); if (!($customFieldSyncResult['ok'] ?? false)) { $errorBag->merge($customFieldSyncResult['errors'] ?? []); $customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds); } if ($currentUserId === $userId && isset($form['theme'])) { $themes = appThemes(); $themeValue = strtolower(trim((string) ($form['theme'] ?? ''))); $userSession = $sessionStore->get('user', []); if (is_array($userSession)) { $userSession['theme'] = isset($themes[$themeValue]) ? $themeValue : appDefaultTheme(); $sessionStore->set('user', $userSession); } } if ($currentUserId === $userId && isset($form['locale'])) { $userSession = $sessionStore->get('user', []); if (is_array($userSession)) { $userSession['locale'] = $form['locale']; $sessionStore->set('user', $userSession); } I18n::$locale = $form['locale']; } if (!$errorBag->hasAny()) { $action = (string) ($post['action'] ?? 'save'); if ($action === 'save_close') { Flash::success('User updated', $closeTarget, 'user_updated'); Router::redirect($closeTarget); } else { Flash::success('User updated', $editTarget, 'user_updated'); Router::redirect($editTarget); } } } } $validationSummaryErrors = $errorBag->toArray(); $errors = $errorBag->toFlatList(); Buffer::set('title', $isOwnAccount ? t('My account') : t('Edit user'));