all(); Guard::requireLogin(); $authorizationService = app(\MintyPHP\Service\Access\AuthorizationService::class); $decision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_VIEW, [ 'actor_user_id' => (int) ($session['user']['id'] ?? 0), ]); if (!$decision->isAllowed()) { if (Request::wantsJson()) { http_response_code($decision->status()); Router::json(['error' => $decision->error()]); return; } Router::redirect('error/forbidden?url=' . urlencode(Request::pathWithQuery())); return; } $filterSchema = require __DIR__ . '/filter-schema.php'; $query = requestInput()->queryAll(); $filterState = gridParseFilters($query, [ 'tenant' => ['type' => 'string'], 'roles' => ['type' => 'csv_ids', 'max' => 200, 'return' => 'array'], 'departments' => ['type' => 'csv_ids', 'max' => 200, 'return' => 'array'], 'active' => ['type' => 'string', 'default' => 'all'], 'email_verified' => ['type' => 'string', 'default' => 'all'], 'login_status' => ['type' => 'string', 'default' => 'all'], 'created_from' => ['type' => 'date'], 'created_to' => ['type' => 'date'], 'search' => ['type' => 'string'], ]); $currentUserId = (int) ($session['user']['id'] ?? 0); $allowedTenantIds = app(\MintyPHP\Service\Tenant\TenantScopeService::class)->getUserTenantIds($currentUserId); $uiAccessService = app(UiAccessService::class); $selfEditDecision = $authorizationService->authorize(UserAuthorizationPolicy::ABILITY_ADMIN_USERS_EDIT_CONTEXT, [ 'actor_user_id' => $currentUserId, 'target_user_id' => $currentUserId, ]); $selfEditCapabilities = $selfEditDecision->attribute('capabilities', []); $viewAuth['page'] = [ ...$uiAccessService->pageCapabilities($currentUserId, UiCapabilityMap::PAGE_USERS_INDEX), 'can_update_self' => $selfEditDecision->isAllowed() && is_array($selfEditCapabilities) && (bool) ($selfEditCapabilities['can_edit_user'] ?? false), ]; Buffer::set('title', t('Users')); $breadcrumbs = [ ['label' => t('Home'), 'path' => 'admin'], ['label' => t('Users')], ]; Buffer::set('grid_lang', json_encode(gridLang(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); $csrfKey = Session::$csrfSessionKey; $csrfToken = $session[$csrfKey] ?? ''; Buffer::set( 'grid_csrf', json_encode(['key' => $csrfKey, 'token' => $csrfToken], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ); $tenants = app(\MintyPHP\Service\Tenant\TenantService::class)->list(); if ($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 (app(\MintyPHP\Service\Tenant\TenantScopeService::class)->isStrict()) { $tenants = []; } sortByDescription($tenants); $roles = app(\MintyPHP\Service\Access\RoleService::class)->listActive(); sortByDescription($roles); $departments = $allowedTenantIds ? app(\MintyPHP\Service\Org\DepartmentService::class)->listByTenantIds($allowedTenantIds) : []; if (!$allowedTenantIds && !app(\MintyPHP\Service\Tenant\TenantScopeService::class)->isStrict()) { $departments = app(\MintyPHP\Service\Org\DepartmentService::class)->list(); } sortByDescription($departments); $toolbarOptionSets = [ 'department_items' => array_map(static fn (array $item): array => [ 'id' => (string) ($item['id'] ?? ''), 'description' => (string) ($item['description'] ?? ''), 'translate' => false, ], $departments), 'role_items' => array_map(static fn (array $item): array => [ 'id' => (string) ($item['id'] ?? ''), 'description' => (string) ($item['description'] ?? ''), 'translate' => false, ], $roles), ]; $toolbarFilterStateOverrides = [ 'tenant' => (string) ($filterState['tenant'] ?? ''), 'roles' => array_map('strval', (array) ($filterState['roles'] ?? [])), 'departments' => array_map('strval', (array) ($filterState['departments'] ?? [])), 'active' => (string) ($filterState['active'] ?? 'all'), 'email_verified' => (string) ($filterState['email_verified'] ?? 'all'), 'login_status' => (string) ($filterState['login_status'] ?? 'all'), 'created_from' => (string) ($filterState['created_from'] ?? ''), 'created_to' => (string) ($filterState['created_to'] ?? ''), 'search' => (string) ($filterState['search'] ?? ''), ]; $listFilterContext = gridBuildListFilterContext($filterSchema, [ 'filter_state' => $filterState, 'search_keys' => ['tenant', 'search'], 'toolbar_state_overrides' => $toolbarFilterStateOverrides, 'toolbar_option_sets' => $toolbarOptionSets, ]); $toolbarFilterSchema = $listFilterContext['toolbarFilterSchema']; $toolbarFilterState = $listFilterContext['toolbarFilterState']; $searchToolbarFilterSchema = $listFilterContext['searchToolbarFilterSchema']; $drawerToolbarFilterSchema = $listFilterContext['drawerToolbarFilterSchema']; $schemaByKey = $listFilterContext['schemaByKey']; $toolbarOptionSets = $listFilterContext['toolbarOptionSets']; $filterChipMeta = [ 'search' => [ 'label' => 'Search', 'type' => 'text', ], 'departments' => [ 'label' => 'Departments', 'type' => 'multi_csv', 'options' => gridOptionMapFromItems((array) ($toolbarOptionSets['department_items'] ?? [])), ], 'roles' => [ 'label' => 'Roles', 'type' => 'multi_csv', 'options' => gridOptionMapFromItems((array) ($toolbarOptionSets['role_items'] ?? [])), ], 'active' => [ 'label' => t('Status'), 'type' => 'select', 'default' => (string) (($schemaByKey['active']['default'] ?? 'all')), 'options' => gridOptionMapFromAllowed((array) ($schemaByKey['active'] ?? [])), ], 'email_verified' => [ 'label' => t('Email verified'), 'type' => 'select', 'default' => (string) (($schemaByKey['email_verified']['default'] ?? 'all')), 'options' => gridOptionMapFromAllowed((array) ($schemaByKey['email_verified'] ?? [])), ], 'login_status' => [ 'label' => t('Login status'), 'type' => 'select', 'default' => (string) (($schemaByKey['login_status']['default'] ?? 'all')), 'options' => gridOptionMapFromAllowed((array) ($schemaByKey['login_status'] ?? [])), ], 'created_range' => [ 'label' => 'Created', 'type' => 'date_range', 'from_param' => 'created_from', 'to_param' => 'created_to', ], ]; $clientFilterSchema = $listFilterContext['clientFilterSchema']; $searchConfig = $listFilterContext['searchConfig'];