Files
breadcrumb-the-shire/pages/admin/users/edit($id).php

312 lines
14 KiB
PHP
Raw Normal View History

2026-02-04 23:31:53 +01:00
<?php
use MintyPHP\Buffer;
use MintyPHP\Http\SessionStoreInterface;
2026-02-23 12:58:19 +01:00
use MintyPHP\I18n;
2026-02-11 19:28:12 +01:00
use MintyPHP\Repository\Auth\PasswordResetRepository;
use MintyPHP\Repository\Auth\RememberTokenRepository;
2026-02-23 12:58:19 +01:00
use MintyPHP\Router;
use MintyPHP\Service\Access\UserAuthorizationPolicy;
2026-02-23 12:58:19 +01:00
use MintyPHP\Service\CustomField\UserCustomFieldValueService;
use MintyPHP\Session;
2026-02-23 12:58:19 +01:00
use MintyPHP\Support\Flash;
use MintyPHP\Support\Guard;
2026-02-04 23:31:53 +01:00
$sessionStore = app(SessionStoreInterface::class);
$session = $sessionStore->all();
2026-02-04 23:31:53 +01:00
Guard::requireLogin();
2026-03-04 15:56:58 +01:00
$request = requestInput();
$returnTarget = requestResolveReturnTarget();
$closeTarget = requestResolveReturnTarget('admin/users');
2026-03-04 15:56:58 +01:00
$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);
2026-02-23 12:58:19 +01:00
$passwordMinLength = $userPasswordPolicyService->minLength();
$passwordHints = $userPasswordPolicyService->hints();
2026-02-04 23:31:53 +01:00
$currentUserId = (int) ($session['user']['id'] ?? 0);
2026-02-04 23:31:53 +01:00
$uuid = trim((string) ($id ?? ''));
$editTarget = requestPathWithReturnTarget("admin/users/edit/{$uuid}", $returnTarget);
2026-02-23 12:58:19 +01:00
$user = $uuid !== '' ? $userAccountService->findByUuid($uuid) : null;
2026-02-04 23:31:53 +01:00
if (!$user) {
Flash::error('User not found', $closeTarget, 'user_not_found');
Router::redirect($closeTarget);
2026-02-04 23:31:53 +01:00
}
$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()) {
2026-02-04 23:31:53 +01:00
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);
$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) {
2026-02-04 23:31:53 +01:00
Router::redirect('error/forbidden');
return;
}
if ($canViewUserMeta || $canViewUserAudit) {
app(\MintyPHP\Service\Audit\AuditMetadataEnricherInterface::class)->enrich($user, ['created_by', 'modified_by', 'active_changed_by']);
2026-02-04 23:31:53 +01:00
}
2026-03-04 15:56:58 +01:00
$errorBag = formErrors();
2026-02-04 23:31:53 +01:00
$errors = [];
$form = $user;
$scopeGateway = app(\MintyPHP\Service\Tenant\TenantScopeService::class);
$strictTenantScope = $scopeGateway->isStrict();
2026-02-04 23:31:53 +01:00
2026-03-04 15:56:58 +01:00
$tenants = $tenantService->list();
if (is_array($allowedTenantIds)) {
2026-02-04 23:31:53 +01:00
$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) {
2026-02-04 23:31:53 +01:00
$tenants = [];
}
2026-02-23 12:58:19 +01:00
$selectedTenantIds = $userTenantRepository->listTenantIdsByUserId($userId);
if (is_array($allowedTenantIds)) {
2026-02-04 23:31:53 +01:00
$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;
}
2026-03-04 15:56:58 +01:00
$roles = $roleService->listActive();
2026-03-13 21:11:48 +01:00
$assignableRoleService = app(\MintyPHP\Service\Access\AssignableRoleService::class);
$assignableRoleIds = $assignableRoleService->listAssignableRoleIdsForActor($currentUserId);
$assignableRoleIdMap = array_fill_keys($assignableRoleIds, true);
$roles = array_values(array_filter($roles, static fn (array $r): bool => isset($assignableRoleIdMap[(int) ($r['id'] ?? 0)])));
2026-02-23 12:58:19 +01:00
$selectedRoleIds = $userRoleRepository->listRoleIdsByUserId($userId);
2026-03-13 21:11:48 +01:00
$visibleSelectedRoleIds = array_values(array_intersect($selectedRoleIds, $assignableRoleIds));
$permissionRows = $canViewPermissionsTable
? $rolePermissionRepository->listPermissionsWithRolesByRoleIds($selectedRoleIds)
: [];
2026-02-23 12:58:19 +01:00
$selectedDepartmentIds = $userDepartmentRepository->listDepartmentIdsByUserId($userId);
2026-03-04 15:56:58 +01:00
$departmentOptionsByTenant = $departmentService->groupActiveByTenantIds($selectedTenantIds);
2026-03-04 15:56:58 +01:00
$customFieldDefinitionsByTenant = $userCustomFieldValueService->buildDefinitionsByTenant($selectedTenantIds);
$customFieldDefinitionIds = [];
foreach ($customFieldDefinitionsByTenant as $tenantDefinitions) {
foreach ($tenantDefinitions as $definition) {
$definitionId = (int) ($definition['id'] ?? 0);
if ($definitionId > 0) {
$customFieldDefinitionIds[] = $definitionId;
}
}
2026-02-04 23:31:53 +01:00
}
2026-03-04 15:56:58 +01:00
$customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds);
$customFieldPostedValues = [
'custom_field_values' => [],
'custom_field_values_multi' => [],
];
2026-02-11 19:28:12 +01:00
$passwordResets = [];
$rememberTokens = [];
$apiTokens = [];
if ($canViewSecurityArtifacts) {
$passwordResets = $passwordResetRepository->listByUserId($userId, 25);
$rememberTokens = $rememberTokenRepository->listByUserId($userId, 25);
2026-02-23 12:58:19 +01:00
$apiTokens = $apiTokenService->listForUser($userId);
}
$canManageApiTokens = $canManageApiTokens && $canViewSecurityArtifacts;
2026-03-13 09:49:11 +01:00
$showApiTokens = $canViewSecurityArtifacts;
2026-02-04 23:31:53 +01:00
if ($request->isMethod('POST') && !Session::checkCsrfToken()) {
Flash::error(t('Form expired, please try again'), $editTarget, 'csrf_expired');
Router::redirect($editTarget);
return;
}
if ($request->isMethod('POST')) {
2026-03-04 15:56:58 +01:00
$post = $request->bodyAll();
$submitDecision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_EDIT_SUBMIT, [
'actor_user_id' => $currentUserId,
'target_user_id' => $userId,
2026-03-04 15:56:58 +01:00
'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']) {
2026-02-04 23:31:53 +01:00
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);
2026-03-04 15:56:58 +01:00
$result = $userAccountService->updateFromAdmin($userId, $post, $currentUserId);
2026-02-04 23:31:53 +01:00
$form = $result['form'] ?? $form;
2026-03-04 15:56:58 +01:00
$errorBag->merge($result['errors'] ?? []);
$tenantIds = $userAssignmentService->normalizeIdInput($post['tenant_ids'] ?? []);
2026-02-23 12:58:19 +01:00
$existingTenantIds = $userTenantRepository->listTenantIdsByUserId($userId);
$tenantIdsForSync = $tenantIds;
if ($canEditAssignments && is_array($allowedTenantIds)) {
2026-02-04 23:31:53 +01:00
$tenantIds = array_values(array_intersect($tenantIds, $allowedTenantIds));
$tenantIdsForSync = $scopeGateway->mergeTenantIdsPreservingOutOfScope(
$tenantIds,
$existingTenantIds,
$allowedTenantIds
);
} elseif ($canEditAssignments && !$canManageTenants && $strictTenantScope) {
2026-02-04 23:31:53 +01:00
$tenantIds = [];
$tenantIdsForSync = $existingTenantIds;
}
if (!$canEditAssignments) {
2026-02-23 12:58:19 +01:00
$tenantIds = $userTenantRepository->listTenantIdsByUserId($userId);
if (is_array($allowedTenantIds)) {
$tenantIds = array_values(array_intersect($tenantIds, $allowedTenantIds));
}
$tenantIdsForSync = $tenantIds;
2026-02-04 23:31:53 +01:00
}
2026-02-04 23:31:53 +01:00
$selectedTenantIds = $tenantIds;
2026-03-04 15:56:58 +01:00
$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;
}
}
}
2026-03-04 15:56:58 +01:00
$customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds);
$customFieldPostedValues = [
2026-03-04 15:56:58 +01:00
'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'] : [],
];
2026-02-04 23:31:53 +01:00
if ($result['ok'] ?? false) {
if ($canEditAssignments) {
$assignmentsBefore = $userAssignmentService->buildAssignmentsForUser($userId);
2026-03-04 15:56:58 +01:00
$assignmentsSynced = $userAssignmentService->syncAllAssignments(
$userId,
$tenantIdsForSync,
$selectedRoleIds,
2026-03-13 21:11:48 +01:00
$selectedDepartmentIds,
$currentUserId
2026-03-04 15:56:58 +01:00
);
if (!$assignmentsSynced) {
$errorBag->addGlobal(t('User assignments can not be updated'));
} else {
$assignmentsAfter = $userAssignmentService->buildAssignmentsForUser($userId);
$userAccountService->dispatchAssignmentChangedIfNeeded(
$userId,
$assignmentsBefore,
$assignmentsAfter,
$currentUserId
);
}
2026-02-04 23:31:53 +01:00
2026-03-04 15:56:58 +01:00
if ($currentUserId === $userId && !$errorBag->hasAny()) {
2026-02-23 12:58:19 +01:00
$authService->loadTenantDataIntoSession($userId);
2026-02-04 23:31:53 +01:00
}
}
2026-03-04 15:56:58 +01:00
$customFieldSyncResult = $userCustomFieldValueService->syncForUser(
$userId,
$selectedTenantIds,
2026-03-04 15:56:58 +01:00
$post,
$canEditCustomFieldValues
);
if (!($customFieldSyncResult['ok'] ?? false)) {
2026-03-04 15:56:58 +01:00
$errorBag->merge($customFieldSyncResult['errors'] ?? []);
$customFieldValueMap = $userCustomFieldValueService->buildUserValueMap($userId, $customFieldDefinitionIds);
}
2026-02-04 23:31:53 +01:00
if ($currentUserId === $userId && isset($form['theme'])) {
2026-02-11 19:28:12 +01:00
$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);
}
2026-02-04 23:31:53 +01:00
}
if ($currentUserId === $userId && isset($form['locale'])) {
$userSession = $sessionStore->get('user', []);
if (is_array($userSession)) {
$userSession['locale'] = $form['locale'];
$sessionStore->set('user', $userSession);
}
2026-02-04 23:31:53 +01:00
I18n::$locale = $form['locale'];
}
2026-03-04 15:56:58 +01:00
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);
}
2026-02-04 23:31:53 +01:00
}
}
}
2026-03-04 15:56:58 +01:00
$validationSummaryErrors = $errorBag->toArray();
$errors = $errorBag->toFlatList();
$titleText = $isOwnAccount ? t('My account') : ($canEditUser ? t('Edit user') : t('View user'));
Buffer::set('title', $titleText);
if (!($isOwnAccount && !$canViewUsers)) {
$breadcrumbs = [
['label' => t('Home'), 'path' => 'admin'],
['label' => t('Users'), 'path' => 'admin/users'],
['label' => $titleText],
];
}