Initial commit
This commit is contained in:
546
app/Http/Livewire/OnlineReactedProfiles.php
Normal file
546
app/Http/Livewire/OnlineReactedProfiles.php
Normal file
@@ -0,0 +1,546 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user