router; } public function vote(string $attribute, array $bookmark, BackendUserAuthentication $user): bool { return match ($attribute) { self::READ => $this->canRead($bookmark, $user), self::CREATE => $this->canCreate($user), self::EDIT => $this->canEdit($bookmark, $user), self::DELETE => $this->canDelete($bookmark, $user), self::NAVIGATE => $this->canNavigate($bookmark, $user), default => false, }; } private function canCreate(BackendUserAuthentication $user): bool { return isset($user->user['uid']) && (int)$user->user['uid'] > 0; } private function canRead(array $bookmark, BackendUserAuthentication $user): bool { $userId = (int)$user->user['uid']; // User's own bookmarks are always readable if ((int)$bookmark['userid'] === $userId) { return true; } // Global bookmarks are only readable if user can navigate to them if ((int)$bookmark['sc_group'] < 0) { return $this->canNavigate($bookmark, $user); } return false; } private function canEdit(array $bookmark, BackendUserAuthentication $user): bool { if (!$this->canRead($bookmark, $user)) { return false; } $userId = (int)$user->user['uid']; $isOwner = (int)$bookmark['userid'] === $userId; $groupId = isset($bookmark['group_uuid']) && $bookmark['group_uuid'] !== '' ? $bookmark['group_uuid'] : (int)$bookmark['sc_group']; $isGlobal = is_int($groupId) && $groupId < 0; if ($isOwner && !$isGlobal) { return true; } if ($user->isAdmin() && $isGlobal) { return true; } return false; } private function canDelete(array $bookmark, BackendUserAuthentication $user): bool { $userId = (int)$user->user['uid']; if ((int)$bookmark['userid'] === $userId) { return true; } if ($user->isAdmin() && (int)$bookmark['sc_group'] < 0) { return true; } return false; } private function canNavigate(array $bookmark, BackendUserAuthentication $user): bool { $routeIdentifier = $bookmark['route'] ?? ''; try { $arguments = json_decode($bookmark['arguments'] ?? '', true, 64, JSON_THROW_ON_ERROR); } catch (\JsonException) { return false; } if (!is_array($arguments)) { return false; } $moduleName = $this->getModuleNameFromRouteIdentifier($routeIdentifier); if ($moduleName === '') { return false; } if (!$this->canAccessModule($routeIdentifier, $moduleName, $user)) { return false; } if (!$this->canAccessFile($moduleName, $arguments)) { return false; } if (!$this->canAccessRecord($moduleName, $routeIdentifier, $arguments, $user)) { return false; } return true; } private function canAccessModule(string $routeIdentifier, string $moduleName, BackendUserAuthentication $user): bool { // record_edit has its own access checks via canAccessRecord if ($routeIdentifier === 'record_edit') { return true; } return $this->moduleProvider->accessGranted($moduleName, $user); } private function canAccessFile(string $moduleName, array $arguments): bool { if ($moduleName !== 'file_FilelistList' && $moduleName !== 'media_management') { return true; } $combinedIdentifier = (string)($arguments['id'] ?? ''); if ($combinedIdentifier === '') { return true; } $storage = $this->storageRepository->findByCombinedIdentifier($combinedIdentifier); if ($storage === null || $storage->isFallbackStorage()) { return false; } $folderIdentifier = substr($combinedIdentifier, strpos($combinedIdentifier, ':') + 1); try { $storage->getFolder($folderIdentifier); } catch (InsufficientFolderAccessPermissionsException) { return false; } catch (FolderDoesNotExistException) { return true; } catch (\Throwable $e) { $this->logger->error('Failed to resolve folder identifier "{folder}" in backend user bookmark: {message}', [ 'folder' => $folderIdentifier, 'message' => $e->getMessage(), ]); return false; } return true; } private function canAccessRecord( string $moduleName, string $routeIdentifier, array $arguments, BackendUserAuthentication $user ): bool { if ($moduleName === 'file_FilelistList' || $moduleName === 'media_management') { return true; } $bookmarkData = $this->parseRecordEditData($routeIdentifier, $arguments); $pageId = 0; if ($moduleName === 'record_edit' && isset($bookmarkData['table'], $bookmarkData['recordid'])) { if (!$user->check('tables_modify', $bookmarkData['table'])) { return false; } $action = $bookmarkData['action'] ?? ''; $recordId = (int)$bookmarkData['recordid']; if ($action === 'edit' || ($action === 'new' && $recordId < 0)) { $record = BackendUtility::getRecord($bookmarkData['table'], abs($recordId)); if ($record === null || $record === []) { return false; } $pageId = ($bookmarkData['table'] === 'pages' ? (int)($record['uid'] ?? 0) : (int)($record['pid'] ?? 0)); } elseif ($action === 'new' && $recordId > 0) { $pageId = $recordId; } } else { $pageId = (int)($arguments['id'] ?? 0); } if ($pageId > 0 && !$user->isAdmin()) { if ($user->isInWebMount($pageId) === null) { return false; } $pageRow = BackendUtility::getRecord('pages', $pageId); if ($pageRow === null || !$user->doesUserHaveAccess($pageRow, Permission::PAGE_SHOW)) { return false; } } return true; } }