sendMessage($conversation->id, $senderId, $content, $metadata); } /** * Send a message to a conversation */ public function sendMessage($conversationId, $senderId, string $content, array $metadata = []): Message { $message = Message::create([ 'conversation_id' => $conversationId, 'sender_id' => $senderId, 'content' => $content, 'type' => 'text', 'is_system' => false, 'metadata' => $metadata, ]); // Update conversation last message time Conversation::where('id', $conversationId)->update([ 'last_message_at' => now(), ]); // Fire event for notifications Event::dispatch('socialbase.message.sent', [$message]); // Fire moderation hook - allows extensions to auto-moderate $this->runModerationHooks($message); return $message; } /** * Run moderation hooks to allow extensions to auto-moderate messages */ private function runModerationHooks(Message $message): void { // Dispatch moderation event - extensions can return actions $actions = Event::dispatch('socialbase.message.moderation', [$message]); // Process actions from extensions foreach ($actions as $action) { if (empty($action) || !is_array($action)) { continue; } $actionType = $action['action'] ?? null; $reason = $action['reason'] ?? 'Automated moderation'; switch ($actionType) { case 'flag': $this->flagMessage($message->id, null, $reason); break; case 'delete': $this->deleteMessage($message->id, null, $reason); break; case 'approve': $this->approveMessage($message->id, null); break; // 'keep' or null means do nothing } } } /** * Send a system message to a user */ public function sendSystemMessage($userId, string $content, string $systemType = 'general', array $metadata = []): Message { // Validate user exists if (!User::find($userId)) { throw new \InvalidArgumentException("User with ID {$userId} does not exist"); } // Find or create system conversation for THIS specific user // We need to find a system conversation that has ONLY this user as participant $conversation = Conversation::where('type', 'system') ->whereNull('created_by') ->whereHas('participants', function ($query) use ($userId) { $query->where('user_id', $userId); }) ->withCount('participants') ->having('participants_count', '=', 1) ->first(); if (!$conversation) { // Check if there are any orphaned system conversations for this user $this->cleanupOrphanedSystemConversations($userId); // Create a new system conversation for this user $conversation = Conversation::create([ 'type' => 'system', 'created_by' => null, 'subject' => 'System Messages', ]); // Add user as participant $conversation->participants()->attach($userId); // Log the creation for debugging \Log::info("Created new system conversation {$conversation->id} for user {$userId}"); } $message = Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => null, 'content' => $content, 'type' => 'system', 'is_system' => true, 'system_type' => $systemType, 'metadata' => $metadata, ]); // Update conversation last message time $conversation->update(['last_message_at' => now()]); // Fire event for notifications Event::dispatch('socialbase.system_message.sent', [$message, $userId]); return $message; } /** * Clean up orphaned system conversations for a user */ private function cleanupOrphanedSystemConversations($userId): void { // Find all system conversations for this user $conversations = Conversation::where('type', 'system') ->whereNull('created_by') ->whereHas('participants', function ($query) use ($userId) { $query->where('user_id', $userId); }) ->with(['participants', 'messages']) ->get(); if ($conversations->count() <= 1) { return; // No cleanup needed } \Log::warning("Found {$conversations->count()} system conversations for user {$userId}, consolidating..."); // Sort by creation date to keep the oldest as the main conversation $conversations = $conversations->sortBy('created_at'); $mainConversation = $conversations->first(); $conversationsToMerge = $conversations->skip(1); foreach ($conversationsToMerge as $conversationToMerge) { // Move all messages from the conversation to merge to the main conversation $messages = $conversationToMerge->messages; foreach ($messages as $message) { $message->update(['conversation_id' => $mainConversation->id]); } // Update the main conversation's last message time if needed $latestMessage = $mainConversation->messages()->latest()->first(); if ($latestMessage) { $mainConversation->update(['last_message_at' => $latestMessage->created_at]); } // Delete the conversation to merge $conversationToMerge->delete(); \Log::info("Merged conversation {$conversationToMerge->id} into {$mainConversation->id} for user {$userId}"); } } /** * Send system message to multiple users */ public function sendSystemMessageToUsers(array $userIds, string $content, string $systemType = 'general', array $metadata = []): array { $messages = []; foreach ($userIds as $userId) { $messages[] = $this->sendSystemMessage($userId, $content, $systemType, $metadata); } return $messages; } /** * Get user's conversations */ public function getUserConversations($userId, $includeArchived = false) { $query = Conversation::whereHas('participants', function ($q) use ($userId, $includeArchived) { $q->where('user_id', $userId); if (!$includeArchived) { $q->where('is_archived', false); } }) ->with(['lastMessage', 'participants']) ->orderBy('last_message_at', 'desc'); return $query; } /** * Get unread messages count for user */ public function getUnreadCount($userId): int { $conversations = Conversation::whereHas('participants', function ($q) use ($userId) { $q->where('user_id', $userId); })->get(); $total = 0; foreach ($conversations as $conversation) { $total += $conversation->getUnreadCountForUser($userId); } return $total; } /** * Flag message for moderation */ public function flagMessage($messageId, $flaggedBy, string $reason = null): void { $message = Message::findOrFail($messageId); $message->update([ 'is_moderated' => true, 'moderation_status' => 'flagged', 'moderation_reason' => $reason, ]); Event::dispatch('socialbase.message.flagged', [$message, $flaggedBy]); } /** * Approve message */ public function approveMessage($messageId, $moderatorId): void { $message = Message::findOrFail($messageId); $message->update([ 'is_moderated' => true, 'moderation_status' => 'approved', 'moderated_by' => $moderatorId, 'moderated_at' => now(), ]); Event::dispatch('socialbase.message.approved', [$message, $moderatorId]); } /** * Delete message (soft delete) */ public function deleteMessage($messageId, $moderatorId, string $reason = null): void { $message = Message::findOrFail($messageId); $message->update([ 'is_moderated' => true, 'moderation_status' => 'deleted', 'moderated_by' => $moderatorId, 'moderated_at' => now(), 'moderation_reason' => $reason, ]); $message->delete(); Event::dispatch('socialbase.message.deleted', [$message, $moderatorId]); } /** * Report a message */ public function reportMessage($messageId, $reportedBy, string $reason, string $description = null) { $report = \Paymenter\Extensions\Others\SocialBase\Models\MessageReport::create([ 'message_id' => $messageId, 'reported_by' => $reportedBy, 'reason' => $reason, 'description' => $description, 'status' => 'pending', ]); // Auto-flag the message if it gets multiple reports $message = Message::findOrFail($messageId); $reportCount = $message->reports()->where('status', 'pending')->count(); if ($reportCount >= 3 && $message->moderation_status !== 'flagged') { $this->flagMessage($messageId, null, "Automatically flagged after {$reportCount} user reports"); } Event::dispatch('socialbase.message.reported', [$message, $report]); return $report; } /** * Get message reports */ public function getMessageReports($messageId) { return \Paymenter\Extensions\Others\SocialBase\Models\MessageReport::where('message_id', $messageId) ->with(['reporter', 'reviewer']) ->orderBy('created_at', 'desc') ->get(); } /** * Check for and consolidate duplicate system conversations for all users */ public function consolidateSystemConversations(): array { $results = []; // Get all users $users = User::all(); foreach ($users as $user) { $conversations = Conversation::where('type', 'system') ->whereNull('created_by') ->whereHas('participants', function ($query) use ($user) { $query->where('user_id', $user->id); }) ->with(['participants', 'messages']) ->get(); if ($conversations->count() <= 1) { continue; // No consolidation needed } $this->cleanupOrphanedSystemConversations($user->id); $results[] = [ 'user_id' => $user->id, 'user_name' => $user->name, 'conversations_found' => $conversations->count(), 'consolidated' => true ]; } return $results; } /** * Get system conversation statistics */ public function getSystemConversationStats(): array { $systemConversations = Conversation::where('type', 'system') ->whereNull('created_by') ->with(['participants', 'messages']) ->get(); $stats = [ 'total_conversations' => $systemConversations->count(), 'total_messages' => $systemConversations->sum(function ($conv) { return $conv->messages->count(); }), 'users_with_system_conversations' => 0, 'conversations_by_user' => [] ]; // Group by user foreach ($systemConversations as $conversation) { foreach ($conversation->participants as $participant) { $userId = $participant->pivot->user_id; // Get user_id from pivot table if ($userId) { // Only count if user_id is not null/empty if (!isset($stats['conversations_by_user'][$userId])) { $stats['conversations_by_user'][$userId] = 0; } $stats['conversations_by_user'][$userId]++; } } } $stats['users_with_system_conversations'] = count($stats['conversations_by_user']); return $stats; } }