mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
352 lines
9.8 KiB
PHP
352 lines
9.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
namespace OCA\Forum\Service;
|
|
|
|
use OCA\Forum\Db\BBCodeMapper;
|
|
use OCA\Forum\Db\ForumUserMapper;
|
|
use OCA\Forum\Db\Role;
|
|
use OCA\Forum\Db\RoleMapper;
|
|
use OCA\Forum\Db\UserRoleMapper;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\IL10N;
|
|
use OCP\IUserManager;
|
|
|
|
/**
|
|
* Service for user enrichment and display logic
|
|
* Handles Nextcloud user lookups and deleted user display
|
|
*/
|
|
class UserService {
|
|
public function __construct(
|
|
private IUserManager $userManager,
|
|
private ForumUserMapper $forumUserMapper,
|
|
private RoleMapper $roleMapper,
|
|
private UserRoleMapper $userRoleMapper,
|
|
private BBCodeMapper $bbCodeMapper,
|
|
private BBCodeService $bbCodeService,
|
|
private GuestService $guestService,
|
|
private IL10N $l10n,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Get display name for a user
|
|
* Returns "Deleted User" if user doesn't exist in Nextcloud
|
|
*/
|
|
public function getUserDisplayName(string $userId): string {
|
|
$user = $this->userManager->get($userId);
|
|
if ($user !== null) {
|
|
return $user->getDisplayName();
|
|
}
|
|
|
|
// User doesn't exist in Nextcloud - return generic deleted user name
|
|
return $this->l10n->t('Deleted user');
|
|
}
|
|
|
|
/**
|
|
* Check if a user has been deleted
|
|
* Checks both Nextcloud user existence and forum_users deleted_at flag
|
|
*/
|
|
public function isUserDeleted(string $userId): bool {
|
|
// First check if user exists in Nextcloud
|
|
$user = $this->userManager->get($userId);
|
|
if ($user === null) {
|
|
return true;
|
|
}
|
|
|
|
// Check if marked as deleted in forum_users
|
|
try {
|
|
$forumUser = $this->forumUserMapper->find($userId);
|
|
return $forumUser->getDeletedAt() !== null;
|
|
} catch (DoesNotExistException $e) {
|
|
// No forum user record, user is not deleted (just hasn't posted yet)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enrich user data with display name, deleted status, roles, and signature
|
|
*
|
|
* @param string $userId
|
|
* @param array|null $roles Optional pre-fetched roles array
|
|
* @param array|null $bbcodes Optional pre-fetched BBCode definitions for parsing signatures
|
|
* @return array{userId: string, displayName: string, isDeleted: bool, roles: array, signature: ?string, signatureRaw: ?string}
|
|
*/
|
|
public function enrichUserData(string $userId, ?array $roles = null, ?array $bbcodes = null): array {
|
|
// Handle guest authors
|
|
if (GuestService::isGuestAuthor($userId)) {
|
|
$guestDisplayName = $this->guestService->getGuestDisplayName($userId) ?? $this->l10n->t('Guest');
|
|
try {
|
|
$guestRole = $this->roleMapper->findByRoleType(Role::ROLE_TYPE_GUEST);
|
|
$guestRoles = [$guestRole->jsonSerialize()];
|
|
} catch (\Exception $e) {
|
|
$guestRoles = [];
|
|
}
|
|
return [
|
|
'userId' => $userId,
|
|
'displayName' => $guestDisplayName,
|
|
'isDeleted' => false,
|
|
'isGuest' => true,
|
|
'roles' => $guestRoles,
|
|
'signature' => null,
|
|
'signatureRaw' => null,
|
|
];
|
|
}
|
|
|
|
$isDeleted = $this->isUserDeleted($userId);
|
|
$displayName = $this->getUserDisplayName($userId);
|
|
|
|
// If roles not provided, fetch them
|
|
if ($roles === null) {
|
|
$userRoles = $this->userRoleMapper->findByUserId($userId);
|
|
$roles = [];
|
|
foreach ($userRoles as $userRole) {
|
|
try {
|
|
$role = $this->roleMapper->find($userRole->getRoleId());
|
|
$roles[] = $role->jsonSerialize();
|
|
} catch (\Exception $e) {
|
|
// Role not found, skip
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get signature from forum user
|
|
$signatureRaw = null;
|
|
$signature = null;
|
|
try {
|
|
$forumUser = $this->forumUserMapper->find($userId);
|
|
$signatureRaw = $forumUser->getSignature();
|
|
if ($signatureRaw !== null && $signatureRaw !== '') {
|
|
// Parse BBCode in signature
|
|
if ($bbcodes === null) {
|
|
$bbcodes = $this->bbCodeMapper->findAllEnabled();
|
|
}
|
|
$signature = $this->bbCodeService->parse($signatureRaw, $bbcodes);
|
|
}
|
|
} catch (DoesNotExistException $e) {
|
|
// No forum user record, no signature
|
|
}
|
|
|
|
return [
|
|
'userId' => $userId,
|
|
'displayName' => $displayName,
|
|
'isDeleted' => $isDeleted,
|
|
'roles' => $roles,
|
|
'signature' => $signature,
|
|
'signatureRaw' => $signatureRaw,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Enrich multiple users at once (for performance)
|
|
*
|
|
* @param array<string> $userIds
|
|
* @param array<string, array> $rolesMap Optional pre-fetched roles map (userId => roles[])
|
|
* @param array|null $bbcodes Optional pre-fetched BBCode definitions for parsing signatures
|
|
* @return array<string, array{userId: string, displayName: string, isDeleted: bool, roles: array, signature: ?string, signatureRaw: ?string}>
|
|
*/
|
|
public function enrichMultipleUsers(array $userIds, ?array $rolesMap = null, ?array $bbcodes = null): array {
|
|
$result = [];
|
|
|
|
// Separate guest and real user IDs
|
|
$guestIds = [];
|
|
$realUserIds = [];
|
|
foreach ($userIds as $userId) {
|
|
if (GuestService::isGuestAuthor($userId)) {
|
|
$guestIds[] = $userId;
|
|
} else {
|
|
$realUserIds[] = $userId;
|
|
}
|
|
}
|
|
|
|
// Handle guest users
|
|
if (!empty($guestIds)) {
|
|
$guestRoles = [];
|
|
try {
|
|
$guestRole = $this->roleMapper->findByRoleType(Role::ROLE_TYPE_GUEST);
|
|
$guestRoles = [$guestRole->jsonSerialize()];
|
|
} catch (\Exception $e) {
|
|
// Guest role not found
|
|
}
|
|
|
|
foreach ($guestIds as $guestId) {
|
|
$guestDisplayName = $this->guestService->getGuestDisplayName($guestId) ?? $this->l10n->t('Guest');
|
|
$result[$guestId] = [
|
|
'userId' => $guestId,
|
|
'displayName' => $guestDisplayName,
|
|
'isDeleted' => false,
|
|
'isGuest' => true,
|
|
'roles' => $guestRoles,
|
|
'signature' => null,
|
|
'signatureRaw' => null,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Handle real users
|
|
if (!empty($realUserIds)) {
|
|
// If roles not provided, fetch them all at once
|
|
if ($rolesMap === null) {
|
|
$rolesMap = $this->fetchRolesForUsers($realUserIds);
|
|
}
|
|
|
|
// Fetch all forum users at once for signatures
|
|
$signaturesMap = $this->fetchSignaturesForUsers($realUserIds);
|
|
|
|
// Fetch BBCodes once for parsing all signatures (if not provided)
|
|
if ($bbcodes === null) {
|
|
$bbcodes = $this->bbCodeMapper->findAllEnabled();
|
|
}
|
|
|
|
foreach ($realUserIds as $userId) {
|
|
$isDeleted = $this->isUserDeleted($userId);
|
|
$displayName = $this->getUserDisplayName($userId);
|
|
|
|
$signatureRaw = $signaturesMap[$userId] ?? null;
|
|
$signature = null;
|
|
if ($signatureRaw !== null && $signatureRaw !== '') {
|
|
$signature = $this->bbCodeService->parse($signatureRaw, $bbcodes);
|
|
}
|
|
|
|
$result[$userId] = [
|
|
'userId' => $userId,
|
|
'displayName' => $displayName,
|
|
'isDeleted' => $isDeleted,
|
|
'roles' => $rolesMap[$userId] ?? [],
|
|
'signature' => $signature,
|
|
'signatureRaw' => $signatureRaw,
|
|
];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Fetch roles for multiple users efficiently
|
|
*
|
|
* @param array<string> $userIds
|
|
* @return array<string, array> Map of userId => roles[]
|
|
*/
|
|
private function fetchRolesForUsers(array $userIds): array {
|
|
if (empty($userIds)) {
|
|
return [];
|
|
}
|
|
|
|
$rolesMap = [];
|
|
|
|
// Initialize all user IDs with empty arrays
|
|
foreach ($userIds as $userId) {
|
|
$rolesMap[$userId] = [];
|
|
}
|
|
|
|
// Fetch all user roles for these users
|
|
$userRoles = $this->userRoleMapper->findByUserIds($userIds);
|
|
|
|
// Group by user ID and fetch role details
|
|
$roleIds = [];
|
|
$userRolesByUser = [];
|
|
foreach ($userRoles as $userRole) {
|
|
$userId = $userRole->getUserId();
|
|
$roleId = $userRole->getRoleId();
|
|
|
|
if (!isset($userRolesByUser[$userId])) {
|
|
$userRolesByUser[$userId] = [];
|
|
}
|
|
$userRolesByUser[$userId][] = $roleId;
|
|
$roleIds[$roleId] = true;
|
|
}
|
|
|
|
// Fetch all roles at once
|
|
$roles = [];
|
|
$roleEntities = $this->roleMapper->findByIds(array_keys($roleIds));
|
|
foreach ($roleEntities as $role) {
|
|
$roles[$role->getId()] = $role->jsonSerialize();
|
|
}
|
|
|
|
// Map roles to users
|
|
foreach ($userRolesByUser as $userId => $userRoleIds) {
|
|
foreach ($userRoleIds as $roleId) {
|
|
if (isset($roles[$roleId])) {
|
|
$rolesMap[$userId][] = $roles[$roleId];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $rolesMap;
|
|
}
|
|
|
|
/**
|
|
* Search users for autocomplete
|
|
* Returns users matching the search query in the format expected by NcRichContenteditable
|
|
*
|
|
* @param string $search Search query (matches against user ID and display name)
|
|
* @param int $limit Maximum number of results to return
|
|
* @param string|null $excludeUserId User ID to exclude from results (e.g., current user)
|
|
* @return array<array{id: string, label: string, icon: string, source: string}> List of matching users
|
|
*/
|
|
public function searchUsersForAutocomplete(string $search = '', int $limit = 10, ?string $excludeUserId = null): array {
|
|
$results = [];
|
|
$search = strtolower(trim($search));
|
|
|
|
// Use IUserManager to search users
|
|
// The search method searches both user ID and display name
|
|
// Request one extra result in case we need to exclude the current user
|
|
$users = $this->userManager->search($search, $excludeUserId !== null ? $limit + 1 : $limit);
|
|
|
|
foreach ($users as $user) {
|
|
// Skip excluded user (e.g., current user)
|
|
if ($excludeUserId !== null && $user->getUID() === $excludeUserId) {
|
|
continue;
|
|
}
|
|
|
|
$results[] = [
|
|
'id' => $user->getUID(),
|
|
'label' => $user->getDisplayName(),
|
|
'icon' => 'icon-user',
|
|
'source' => 'users',
|
|
];
|
|
|
|
// Stop if we have enough results
|
|
if (count($results) >= $limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Fetch signatures for multiple users efficiently
|
|
*
|
|
* @param array<string> $userIds
|
|
* @return array<string, ?string> Map of userId => signature (raw)
|
|
*/
|
|
private function fetchSignaturesForUsers(array $userIds): array {
|
|
if (empty($userIds)) {
|
|
return [];
|
|
}
|
|
|
|
$signaturesMap = [];
|
|
|
|
// Initialize all user IDs with null
|
|
foreach ($userIds as $userId) {
|
|
$signaturesMap[$userId] = null;
|
|
}
|
|
|
|
// Fetch all forum users for these users
|
|
$forumUsers = $this->forumUserMapper->findByUserIds($userIds);
|
|
|
|
// Extract signatures
|
|
foreach ($forumUsers as $forumUser) {
|
|
$signaturesMap[$forumUser->getUserId()] = $forumUser->getSignature();
|
|
}
|
|
|
|
return $signaturesMap;
|
|
}
|
|
}
|