547 lines
18 KiB
PHP
547 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Livewire;
|
|
|
|
use App\Services\PresenceService;
|
|
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
|
|
use Cog\Laravel\Love\Reaction\Models\Reaction;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\Component;
|
|
|
|
class OnlineReactedProfiles extends Component
|
|
{
|
|
// Configuration properties
|
|
public $reactionTypes = []; // ['Bookmark', 'Star', 'Like'] or single 'Bookmark'
|
|
public $guards = ['web']; // Which guards to check for online users
|
|
public $authGuard; // Current user's guard
|
|
public $showCount = true;
|
|
public $showAvatars = true;
|
|
public $maxDisplay = 20;
|
|
public $headerText; // Header text for the component
|
|
public $refreshInterval = 10;
|
|
public $groupByModel = true; // Group results by model type
|
|
public $lastSeen = false; // Show last seen time
|
|
public $modelLabels = [
|
|
'App\Models\User' => 'Persons',
|
|
'App\Models\Organization' => 'Organizations',
|
|
'App\Models\Bank' => 'Banks',
|
|
'App\Models\Admin' => 'Admins',
|
|
];
|
|
|
|
// Data properties
|
|
public $onlineReactedProfiles = [];
|
|
public $totalCount = 0;
|
|
public $countByType = [];
|
|
|
|
// Logout modal properties
|
|
public $showLogoutModal = false;
|
|
public $selectedProfileId = null;
|
|
public $selectedProfileType = null;
|
|
public $selectedProfileName = null;
|
|
|
|
public function mount(
|
|
$reactionTypes = null,
|
|
$guards = null,
|
|
$showCount = true,
|
|
$showAvatars = true,
|
|
$maxDisplay = 20,
|
|
$header = true,
|
|
$headerText = null,
|
|
$groupByModel = false,
|
|
$lastSeen = false
|
|
) {
|
|
// Setup reaction types
|
|
if ($reactionTypes !== null) {
|
|
if (is_string($reactionTypes)) {
|
|
$this->reactionTypes = array_map('trim', explode(',', $reactionTypes));
|
|
} else {
|
|
$this->reactionTypes = is_array($reactionTypes) ? $reactionTypes : [$reactionTypes];
|
|
}
|
|
} else {
|
|
// Keep as null to show all online users without filtering
|
|
$this->reactionTypes = null;
|
|
}
|
|
|
|
// Setup guards to check
|
|
if ($guards) {
|
|
$this->guards = is_array($guards) ? $guards : [$guards];
|
|
} else {
|
|
// Check all guards by default
|
|
$this->guards = ['web', 'organization', 'bank'];
|
|
}
|
|
|
|
// Determine current auth guard
|
|
$this->authGuard = session('active_guard') ?? 'web';
|
|
|
|
// Set display options
|
|
$this->showCount = $showCount;
|
|
$this->showAvatars = $showAvatars;
|
|
$this->maxDisplay = $maxDisplay;
|
|
$this->groupByModel = $groupByModel;
|
|
$this->lastSeen = $lastSeen;
|
|
|
|
// Load initial data
|
|
$this->loadOnlineReactedProfiles();
|
|
|
|
|
|
// Set header text if required
|
|
if ($header) {
|
|
if ($headerText) {
|
|
$this->headerText = $headerText;
|
|
} else {
|
|
$this->headerText = $this->getHeaderText($this->reactionTypes);
|
|
}
|
|
} else {
|
|
$this->headerText = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the header text based on reaction types.
|
|
* If no reaction types, return a default message.
|
|
*/
|
|
public function getHeaderText($reactionTypes)
|
|
{
|
|
if ($this->reactionTypes === null) {
|
|
// Show all online users
|
|
return trans_choice('messages.profiles_online', $this->totalCount, ['count' => $this->totalCount]);
|
|
} elseif (!empty($reactionTypes)) {
|
|
if (count($reactionTypes) === 1) {
|
|
return trans_choice('messages.' . strtolower($reactionTypes[0]) . '_contacts_online', $this->totalCount, ['count' => $this->totalCount]);
|
|
} elseif (count($reactionTypes) > 1) {
|
|
return trans_choice('messages.reactions_contacts_online', $this->totalCount, ['count' => $this->totalCount]);
|
|
}
|
|
}
|
|
return 'Error: no reaction types found.';
|
|
}
|
|
|
|
|
|
|
|
public function loadOnlineReactedProfiles()
|
|
{
|
|
try {
|
|
// Get authenticated user/profile
|
|
$authUser = auth($this->authGuard)->user();
|
|
|
|
if (!$authUser) {
|
|
$this->onlineReactedProfiles = [];
|
|
$this->totalCount = 0;
|
|
return;
|
|
}
|
|
|
|
// Only require viaLoveReacter if filtering by reactions
|
|
if ($this->reactionTypes !== null && !method_exists($authUser, 'viaLoveReacter')) {
|
|
$this->onlineReactedProfiles = [];
|
|
$this->totalCount = 0;
|
|
return;
|
|
}
|
|
|
|
// Collect all online users from specified guards
|
|
$allOnlineProfiles = collect();
|
|
|
|
foreach ($this->guards as $guard) {
|
|
try {
|
|
$presenceService = app(PresenceService::class);
|
|
$onlineUsers = $presenceService->getOnlineUsers($guard);
|
|
|
|
foreach ($onlineUsers as $userData) {
|
|
// Get the actual model instance
|
|
$model = $this->getModelFromUserData($userData, $guard);
|
|
|
|
// If filtering by reactions, check for love reactant trait
|
|
// If not filtering (reactionTypes is null), just check if model exists
|
|
if ($model && ($this->reactionTypes === null || method_exists($model, 'viaLoveReactant'))) {
|
|
$profileData = $this->buildProfileData($model, $userData, $guard);
|
|
|
|
if ($profileData) {
|
|
$allOnlineProfiles->push($profileData);
|
|
}
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error loading online profiles for guard ' . $guard . ': ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// Filter profiles based on whether we're filtering by reactions or not
|
|
if ($this->reactionTypes === null) {
|
|
// Show all online profiles without reaction filtering
|
|
$reactedProfiles = $allOnlineProfiles;
|
|
} else {
|
|
// Filter profiles that have been reacted to
|
|
$reactedProfiles = $allOnlineProfiles->filter(function ($profile) {
|
|
return $this->hasAuthUserReacted($profile['model']);
|
|
});
|
|
}
|
|
|
|
// Apply display limit
|
|
$limitedProfiles = $reactedProfiles->take($this->maxDisplay);
|
|
|
|
// Group by model if requested
|
|
if ($this->groupByModel) {
|
|
$this->onlineReactedProfiles = $limitedProfiles
|
|
->groupBy('model_type')
|
|
->toArray();
|
|
} else {
|
|
$this->onlineReactedProfiles = $limitedProfiles->values()->toArray();
|
|
}
|
|
|
|
// Update counts
|
|
$this->totalCount = $reactedProfiles->count();
|
|
$this->countByType = $reactedProfiles->groupBy('model_type')->map->count()->toArray();
|
|
|
|
// Dispatch event for other components
|
|
$this->dispatch('online-reacted-profiles-updated', [
|
|
'count' => $this->totalCount,
|
|
'profiles' => $this->onlineReactedProfiles
|
|
]);
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error loading online reacted profiles: ' . $e->getMessage());
|
|
$this->onlineReactedProfiles = [];
|
|
$this->totalCount = 0;
|
|
}
|
|
}
|
|
|
|
protected function getModelFromUserData($userData, $guard)
|
|
{
|
|
try {
|
|
// Get the model class for this guard
|
|
$modelClass = $this->getModelClassForGuard($guard);
|
|
|
|
if (!$modelClass) {
|
|
return null;
|
|
}
|
|
|
|
// Handle both array and object formats
|
|
$userId = is_array($userData) ? ($userData['id'] ?? null) : ($userData->id ?? null);
|
|
|
|
if (!$userId) {
|
|
return null;
|
|
}
|
|
|
|
return $modelClass::find($userId);
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error getting model from user data: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected function getModelClassForGuard($guard)
|
|
{
|
|
// Map guards to model classes
|
|
$guardModelMap = [
|
|
'web' => \App\Models\User::class,
|
|
'organization' => \App\Models\Organization::class,
|
|
'bank' => \App\Models\Bank::class,
|
|
'admin' => \App\Models\Admin::class,
|
|
];
|
|
|
|
return $guardModelMap[$guard] ?? null;
|
|
}
|
|
|
|
protected function buildProfileData($model, $userData, $guard)
|
|
{
|
|
try {
|
|
// Get reactions for this profile
|
|
$reactions = $this->getReactionsForProfile($model);
|
|
// Build profile data array
|
|
return [
|
|
'id' => $model->id,
|
|
'model' => $model,
|
|
'model_type' => get_class($model),
|
|
'model_label' => $this->modelLabels[get_class($model)] ?? class_basename($model),
|
|
'guard' => $guard,
|
|
'name' => $model->name ?? 'Unknown',
|
|
'location' => $model->getLocationFirst()['name_short'] ?? '',
|
|
'avatar' => $model->profile_photo_path ?? null,
|
|
'last_seen' => is_array($userData) ?
|
|
($userData['last_seen'] ?? now()) : ($userData->last_seen ?? now()),
|
|
'reactions' => $reactions,
|
|
'profile_url' => route('profile.show_by_type_and_id', ['type' => __(strtolower(class_basename($model))), 'id' => $model->id]),
|
|
'is_online' => true,
|
|
];
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error building profile data: ' . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected function hasAuthUserReacted($model)
|
|
{
|
|
try {
|
|
// If no reaction types specified, return true (show all)
|
|
if ($this->reactionTypes === null) {
|
|
return true;
|
|
}
|
|
|
|
if (!method_exists($model, 'viaLoveReactant')) {
|
|
return false;
|
|
}
|
|
|
|
$reactantFacade = $model->viaLoveReactant();
|
|
|
|
// Get the authenticated user directly
|
|
$authUser = auth($this->authGuard)->user();
|
|
if (!$authUser) {
|
|
return false;
|
|
}
|
|
|
|
// Check if empty array (different from null)
|
|
if (is_array($this->reactionTypes) && empty($this->reactionTypes)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($this->reactionTypes as $reactionType) {
|
|
if ($reactantFacade->isReactedBy($authUser, $reactionType)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error checking reactions: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
protected function getReactionsForProfile($model)
|
|
{
|
|
try {
|
|
if (!method_exists($model, 'viaLoveReactant')) {
|
|
return [];
|
|
}
|
|
|
|
$reactantFacade = $model->viaLoveReactant();
|
|
$reactions = [];
|
|
|
|
// Get the authenticated user directly
|
|
$authUser = auth($this->authGuard)->user();
|
|
if (!$authUser) {
|
|
return [];
|
|
}
|
|
|
|
if ($this->reactionTypes !== null) {
|
|
foreach ($this->reactionTypes as $reactionType) {
|
|
if ($reactantFacade->isReactedBy($authUser, $reactionType)) {
|
|
$reactions[] = [
|
|
'type' => $reactionType,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $reactions;
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error getting reactions for profile: ' . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|
|
public function toggleReaction($profileId, $modelType, $reactionType)
|
|
{
|
|
try {
|
|
$authUser = auth($this->authGuard)->user();
|
|
if (!$authUser) {
|
|
return;
|
|
}
|
|
|
|
$model = $modelType::find($profileId);
|
|
if (!$model) {
|
|
return;
|
|
}
|
|
|
|
$reacterFacade = $authUser->viaLoveReacter();
|
|
$reactantFacade = $model->viaLoveReactant();
|
|
|
|
if ($reactantFacade->isReactedBy($authUser, $reactionType)) {
|
|
$reacterFacade->unreactTo($model, $reactionType);
|
|
} else {
|
|
$reacterFacade->reactTo($model, $reactionType);
|
|
}
|
|
|
|
$this->loadOnlineReactedProfiles();
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error toggling reaction: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
#[On('presence-updated')]
|
|
public function refreshProfiles()
|
|
{
|
|
$this->loadOnlineReactedProfiles();
|
|
}
|
|
|
|
#[On('user-activity')]
|
|
public function onUserActivity()
|
|
{
|
|
$this->loadOnlineReactedProfiles();
|
|
}
|
|
|
|
public function isCurrentUserProfile($profileId, $modelType)
|
|
{
|
|
// Get the active profile using getActiveProfile() helper
|
|
$activeProfile = getActiveProfile();
|
|
|
|
if (!$activeProfile) {
|
|
return false;
|
|
}
|
|
|
|
// Check if this is the current active profile (admin or any other type)
|
|
if (get_class($activeProfile) === $modelType || get_class($activeProfile) === '\\' . $modelType) {
|
|
if ($activeProfile->id === $profileId) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If the active profile is an Admin, check for related users
|
|
if (get_class($activeProfile) === \App\Models\Admin::class) {
|
|
if (method_exists($activeProfile, 'users')) {
|
|
$relatedUsers = $activeProfile->users()->pluck('users.id')->toArray();
|
|
|
|
if ($modelType === 'App\Models\User' || $modelType === \App\Models\User::class) {
|
|
return in_array($profileId, $relatedUsers);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function openLogoutModal($profileId, $modelType)
|
|
{
|
|
// Verify admin access
|
|
if (getActiveProfileType() !== 'Admin') {
|
|
$this->dispatch('notify', [
|
|
'type' => 'error',
|
|
'message' => 'Unauthorized action. Only administrators can log out users.'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Prevent logging out current admin or their related profiles
|
|
if ($this->isCurrentUserProfile($profileId, $modelType)) {
|
|
$this->dispatch('notify', [
|
|
'type' => 'error',
|
|
'message' => 'You cannot log out your own profile or related user accounts.'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$this->selectedProfileId = $profileId;
|
|
$this->selectedProfileType = $modelType;
|
|
|
|
// Get profile name for display
|
|
try {
|
|
$model = $modelType::find($profileId);
|
|
$this->selectedProfileName = $model ? $model->name : null;
|
|
} catch (\Exception $e) {
|
|
$this->selectedProfileName = null;
|
|
}
|
|
|
|
$this->showLogoutModal = true;
|
|
}
|
|
|
|
public function closeLogoutModal()
|
|
{
|
|
$this->showLogoutModal = false;
|
|
$this->selectedProfileId = null;
|
|
$this->selectedProfileType = null;
|
|
$this->selectedProfileName = null;
|
|
}
|
|
|
|
public function logoutUser()
|
|
{
|
|
// Verify admin access
|
|
if (getActiveProfileType() !== 'Admin') {
|
|
$this->dispatch('notify', [
|
|
'type' => 'error',
|
|
'message' => 'Unauthorized action. Only administrators can log out users.'
|
|
]);
|
|
$this->closeLogoutModal();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (!$this->selectedProfileId || !$this->selectedProfileType) {
|
|
throw new \Exception('Invalid profile data');
|
|
}
|
|
|
|
$model = $this->selectedProfileType::find($this->selectedProfileId);
|
|
|
|
if (!$model) {
|
|
throw new \Exception('User not found');
|
|
}
|
|
|
|
// Get the guard for this model type
|
|
$guard = $this->getGuardForModelType($this->selectedProfileType);
|
|
|
|
if (!$guard) {
|
|
throw new \Exception('Invalid guard');
|
|
}
|
|
|
|
// Broadcast logout event via websocket - this will force the user's browser to logout
|
|
broadcast(new \App\Events\UserForcedLogout($this->selectedProfileId, $guard));
|
|
|
|
// Delete all sessions for this user from the database
|
|
\DB::connection(config('session.connection'))
|
|
->table(config('session.table', 'sessions'))
|
|
->where('user_id', $this->selectedProfileId)
|
|
->delete();
|
|
|
|
// Clear any cached authentication data
|
|
\Cache::forget('auth_' . $guard . '_' . $this->selectedProfileId);
|
|
|
|
// Clear presence cache
|
|
\Cache::forget("presence_{$guard}_{$this->selectedProfileId}");
|
|
|
|
// Clear online users cache to force refresh
|
|
\Cache::forget("online_users_{$guard}_" . \App\Services\PresenceService::ONLINE_THRESHOLD_MINUTES);
|
|
|
|
// Set user offline in presence service
|
|
$presenceService = app(\App\Services\PresenceService::class);
|
|
$presenceService->setUserOffline($model, $guard);
|
|
|
|
$this->dispatch('notify', [
|
|
'type' => 'success',
|
|
'message' => __('User has been logged out successfully.')
|
|
]);
|
|
|
|
// Refresh the online profiles list
|
|
$this->loadOnlineReactedProfiles();
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error('Error logging out user: ' . $e->getMessage());
|
|
$this->dispatch('notify', [
|
|
'type' => 'error',
|
|
'message' => __('Failed to log out user. Please try again.')
|
|
]);
|
|
}
|
|
|
|
$this->closeLogoutModal();
|
|
}
|
|
|
|
protected function getGuardForModelType($modelType)
|
|
{
|
|
// Map model types to guards
|
|
$modelGuardMap = [
|
|
\App\Models\User::class => 'web',
|
|
'App\Models\User' => 'web',
|
|
\App\Models\Organization::class => 'organization',
|
|
'App\Models\Organization' => 'organization',
|
|
\App\Models\Bank::class => 'bank',
|
|
'App\Models\Bank' => 'bank',
|
|
\App\Models\Admin::class => 'admin',
|
|
'App\Models\Admin' => 'admin',
|
|
];
|
|
|
|
return $modelGuardMap[$modelType] ?? null;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.online-reacted-profiles');
|
|
}
|
|
}
|