Files
2025-11-30 11:57:22 +01:00

399 lines
13 KiB
PHP

<?php
namespace Paymenter\Extensions\Others\SocialBase\Services;
use Paymenter\Extensions\Others\SocialBase\Models\Conversation;
use Paymenter\Extensions\Others\SocialBase\Models\Message;
use App\Models\User;
use Illuminate\Support\Facades\Event;
class MessagingService
{
/**
* Send a message from one user to another
*/
public function sendDirectMessage($senderId, $recipientId, string $content, array $metadata = []): Message
{
$conversation = Conversation::findOrCreateDirectConversation($senderId, $recipientId);
return $this->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;
}
}