286 lines
8.9 KiB
PHP
286 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace Engelsystem\Controllers;
|
|
|
|
use Engelsystem\Database\Database;
|
|
use Engelsystem\Helpers\Authenticator;
|
|
use Engelsystem\Http\Redirector;
|
|
use Engelsystem\Http\Request;
|
|
use Engelsystem\Http\Response;
|
|
use Engelsystem\Models\Message;
|
|
use Engelsystem\Models\User\User;
|
|
use Illuminate\Database\Query\Expression as QueryExpression;
|
|
use Psr\Log\LoggerInterface;
|
|
use Illuminate\Support\Collection;
|
|
use Engelsystem\Http\Exceptions\HttpForbidden;
|
|
|
|
class MessagesController extends BaseController
|
|
{
|
|
/** @var Authenticator */
|
|
protected $auth;
|
|
|
|
/** @var LoggerInterface */
|
|
protected $log;
|
|
|
|
/** @var Redirector */
|
|
protected $redirect;
|
|
|
|
/** @var Response */
|
|
protected $response;
|
|
|
|
/** @var Response */
|
|
protected $request;
|
|
|
|
/** @var Database */
|
|
protected $db;
|
|
|
|
/** @var Message */
|
|
protected $message;
|
|
|
|
/** @var User */
|
|
protected $user;
|
|
|
|
/** @var string[] */
|
|
protected $permissions = [
|
|
'user_messages',
|
|
];
|
|
|
|
/**
|
|
* @param Authenticator $auth
|
|
* @param LoggerInterface $log
|
|
* @param Redirector $redirect
|
|
* @param Response $response
|
|
* @param Request $request
|
|
* @param Database $db
|
|
* @param Message $message
|
|
* @param User $user
|
|
*/
|
|
public function __construct(
|
|
Authenticator $auth,
|
|
LoggerInterface $log,
|
|
Redirector $redirect,
|
|
Response $response,
|
|
Request $request,
|
|
Database $db,
|
|
Message $message,
|
|
User $user
|
|
) {
|
|
$this->auth = $auth;
|
|
$this->log = $log;
|
|
$this->redirect = $redirect;
|
|
$this->response = $response;
|
|
$this->request = $request;
|
|
$this->db = $db;
|
|
$this->message = $message;
|
|
$this->user = $user;
|
|
}
|
|
|
|
public function index(): Response
|
|
{
|
|
return $this->listConversations();
|
|
}
|
|
|
|
/**
|
|
* Returns a list of conversations of the current user, each containing the other participant,
|
|
* the most recent message, and the number of unread messages.
|
|
*/
|
|
public function listConversations(): Response
|
|
{
|
|
$current_user = $this->auth->user();
|
|
|
|
$latest_messages = $this->latestMessagePerConversation($current_user);
|
|
$numberOfUnreadMessages = $this->numberOfUnreadMessagesPerConversation($current_user);
|
|
|
|
$conversations = [];
|
|
foreach ($latest_messages as $msg) {
|
|
$other_user = $msg->user_id == $current_user->id ? $msg->receiver : $msg->sender;
|
|
$unread_messages = $numberOfUnreadMessages[$other_user->id] ?? 0;
|
|
array_push($conversations, [
|
|
'other_user' => $other_user,
|
|
'latest_message' => $msg,
|
|
'unread_messages' => $unread_messages
|
|
]);
|
|
}
|
|
|
|
$users = $this->user->orderBy('name')->get()
|
|
->except($current_user->id)
|
|
->mapWithKeys(function ($u) {
|
|
return [ $u->id => $u->nameWithPronoun() ];
|
|
});
|
|
|
|
return $this->response->withView(
|
|
'pages/messages/overview.twig',
|
|
[
|
|
'conversations' => $conversations,
|
|
'users' => $users
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Forwards to the conversation with the user of the given id.
|
|
*/
|
|
public function toConversation(Request $request): Response
|
|
{
|
|
$data = $this->validate($request, [ 'user_id' => 'required|int' ]);
|
|
return $this->redirect->to('/messages/' . $data['user_id']);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of messages between the current user and a user with the given id. The ids shall not be the same.
|
|
* Unread messages will be marked as read during this call. Still, they will be shown as unread in the frontend to
|
|
* highlight them to the user as new.
|
|
*/
|
|
public function conversation(Request $request): Response
|
|
{
|
|
$current_user = $this->auth->user();
|
|
$other_user = $this->user->findOrFail($request->getAttribute('user_id'));
|
|
|
|
if ($current_user->id == $other_user->id) {
|
|
throw new HttpForbidden('You can not start a conversation with yourself.');
|
|
}
|
|
|
|
$messages = $this->message
|
|
->where(function ($q) use ($current_user, $other_user) {
|
|
$q->whereUserId($current_user->id)
|
|
->whereReceiverId($other_user->id);
|
|
})
|
|
->orWhere(function ($q) use ($current_user, $other_user) {
|
|
$q->whereUserId($other_user->id)
|
|
->whereReceiverId($current_user->id);
|
|
})
|
|
->orderBy('created_at')
|
|
->get();
|
|
|
|
$unread_messages = $messages->filter(function ($m) use ($other_user) {
|
|
return $m->user_id == $other_user->id && !$m->read;
|
|
});
|
|
|
|
foreach ($unread_messages as $msg) {
|
|
$msg->read = true;
|
|
$msg->save();
|
|
$msg->read = false; // change back to true to display it to the frontend one more time.
|
|
}
|
|
|
|
return $this->response->withView(
|
|
'pages/messages/conversation.twig',
|
|
['messages' => $messages, 'other_user' => $other_user]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sends a message to another user.
|
|
*/
|
|
public function send(Request $request): Response
|
|
{
|
|
$current_user = $this->auth->user();
|
|
|
|
$data = $this->validate($request, [ 'text' => 'required' ]);
|
|
|
|
$other_user = $this->user->findOrFail($request->getAttribute('user_id'));
|
|
|
|
if ($other_user->id == $current_user->id) {
|
|
throw new HttpForbidden('You can not send a message to yourself.');
|
|
}
|
|
|
|
$new_message = new Message();
|
|
$new_message->sender()->associate($current_user);
|
|
$new_message->receiver()->associate($other_user);
|
|
$new_message->text = $data['text'];
|
|
$new_message->read = false;
|
|
$new_message->save();
|
|
|
|
$this->log->info(
|
|
'User {from} has written a message to user {to}',
|
|
[
|
|
'from' => $current_user->id,
|
|
'to' => $other_user->id
|
|
]
|
|
);
|
|
return $this->redirect->to('/messages/' . $other_user->id);
|
|
}
|
|
|
|
/**
|
|
* Deletes a message from a given id, as long as this message was send by the current user. The given user_id
|
|
* The given user_id is used to redirect back to the conversation with that user.
|
|
*/
|
|
public function delete(Request $request): Response
|
|
{
|
|
$current_user = $this->auth->user();
|
|
$other_user_id = $request->getAttribute('user_id');
|
|
$msg_id = $request->getAttribute('msg_id');
|
|
$msg = $this->message->findOrFail($msg_id);
|
|
|
|
if ($msg->user_id == $current_user->id) {
|
|
$msg->delete();
|
|
|
|
$this->log->info(
|
|
'User {from} deleted message {msg} in a conversation with user {to}',
|
|
[
|
|
'from' => $current_user->id,
|
|
'to' => $other_user_id,
|
|
'msg' => $msg_id
|
|
]
|
|
);
|
|
} else {
|
|
$this->log->warning(
|
|
'User {from} tried to delete message {msg} which was not written by them, ' .
|
|
'in a conversation with user {to}',
|
|
[
|
|
'from' => $current_user->id,
|
|
'to' => $other_user_id,
|
|
'msg' => $msg_id
|
|
]
|
|
);
|
|
|
|
throw new HttpForbidden('You can not delete a message you haven\'t send');
|
|
}
|
|
|
|
return $this->redirect->to('/messages/' . $other_user_id);
|
|
}
|
|
|
|
public function numberOfUnreadMessages(): int
|
|
{
|
|
return $this->auth->user()
|
|
->messagesReceived()
|
|
->where('read', false)
|
|
->count();
|
|
}
|
|
|
|
protected function numberOfUnreadMessagesPerConversation($current_user): Collection
|
|
{
|
|
return $current_user->messagesReceived()
|
|
->select('user_id', $this->raw('count(*) as amount'))
|
|
->where('read', false)
|
|
->groupBy('user_id')
|
|
->get(['user_id', 'amount'])
|
|
->mapWithKeys(function ($unread) {
|
|
return [ $unread->user_id => $unread->amount ];
|
|
});
|
|
}
|
|
|
|
protected function latestMessagePerConversation($current_user): Collection
|
|
{
|
|
$latest_message_ids = $this->message
|
|
->select($this->raw('max(id) as last_id'))
|
|
->where('user_id', "=", $current_user->id)
|
|
->orWhere('receiver_id', "=", $current_user->id)
|
|
->groupBy($this->raw(
|
|
'(CASE WHEN user_id = ' . $current_user->id .
|
|
' THEN receiver_id ELSE user_id END)'
|
|
));
|
|
|
|
return $this->message
|
|
->joinSub($latest_message_ids, 'conversations', function ($join) {
|
|
$join->on('messages.id', '=', 'conversations.last_id');
|
|
})
|
|
->orderBy('created_at', 'DESC')
|
|
->get();
|
|
}
|
|
|
|
protected function raw($value): QueryExpression
|
|
{
|
|
return $this->db->getConnection()->raw($value);
|
|
}
|
|
}
|