399 lines
13 KiB
PHP
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;
|
|
}
|
|
}
|
|
|