engelsystem/src/Controllers/MessagesController.php

286 lines
8.9 KiB
PHP
Raw Normal View History

<?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);
}
}