Improved Messages UI and shrinking includes/user_messages.php
This commit is contained in:
parent
7d51953b84
commit
2c0d516578
|
@ -50,6 +50,13 @@ $route->post('/questions', 'QuestionsController@delete');
|
||||||
$route->get('/questions/new', 'QuestionsController@add');
|
$route->get('/questions/new', 'QuestionsController@add');
|
||||||
$route->post('/questions/new', 'QuestionsController@save');
|
$route->post('/questions/new', 'QuestionsController@save');
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
$route->get('/messages', 'MessagesController@index');
|
||||||
|
$route->post('/messages', 'MessagesController@toConversation');
|
||||||
|
$route->get('/messages/{user_id:\d+}', 'MessagesController@conversation');
|
||||||
|
$route->post('/messages/{user_id:\d+}', 'MessagesController@send');
|
||||||
|
$route->post('/messages/{user_id:\d+}/{msg_id:\d+}', 'MessagesController@delete');
|
||||||
|
|
||||||
// API
|
// API
|
||||||
$route->get('/api[/{resource:.+}]', 'ApiController@index');
|
$route->get('/api[/{resource:.+}]', 'ApiController@index');
|
||||||
|
|
||||||
|
|
|
@ -1,174 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Engelsystem\Models\Message;
|
use Engelsystem\Controllers\MessagesController;
|
||||||
use Engelsystem\Models\User\User;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function messages_title()
|
|
||||||
{
|
|
||||||
return __('Messages');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function user_unread_messages()
|
function user_unread_messages()
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$count = app()->make(MessagesController::class)
|
||||||
|
->numberOfUnreadMessages();
|
||||||
|
|
||||||
if ($user) {
|
return $count > 0 ? ' <span class="badge bg-danger">' . $count . '</span>' : '';
|
||||||
$new_messages = $user->messagesReceived()
|
|
||||||
->where('read', false)
|
|
||||||
->count();
|
|
||||||
|
|
||||||
if ($new_messages > 0) {
|
|
||||||
return ' <span class="badge bg-danger">' . $new_messages . '</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function user_messages()
|
|
||||||
{
|
|
||||||
$user = auth()->user();
|
|
||||||
$request = request();
|
|
||||||
|
|
||||||
if (!$request->has('action')) {
|
|
||||||
/** @var User[] $users */
|
|
||||||
$users = User::query()
|
|
||||||
->where('user_id', '!=', $user->id)
|
|
||||||
->leftJoin('users_personal_data', 'users.id', '=', 'users_personal_data.user_id')
|
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name', 'pronoun']);
|
|
||||||
|
|
||||||
$to_select_data = [
|
|
||||||
'' => __('Select recipient...')
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($users as $u) {
|
|
||||||
$pronoun = ((config('enable_pronoun') && $u->pronoun) ? ' (' . htmlspecialchars($u->pronoun) . ')' : '');
|
|
||||||
$to_select_data[$u->id] = $u->name . $pronoun;
|
|
||||||
}
|
|
||||||
|
|
||||||
$to_select = html_select_key('to', 'to', $to_select_data, '');
|
|
||||||
|
|
||||||
$messages = $user->messages;
|
|
||||||
|
|
||||||
$messages_table = [
|
|
||||||
[
|
|
||||||
'news' => '',
|
|
||||||
'timestamp' => date(__('Y-m-d H:i')),
|
|
||||||
'from' => User_Nick_render($user),
|
|
||||||
'to' => $to_select,
|
|
||||||
'text' => form_textarea('text', '', ''),
|
|
||||||
'actions' => form_submit('submit', __('Send'))
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($messages as $message) {
|
|
||||||
$sender_user_source = $message->user;
|
|
||||||
$receiver_user_source = $message->receiver;
|
|
||||||
|
|
||||||
$messages_table_entry = [
|
|
||||||
'new' => !$message->read ? icon('envelope') : '',
|
|
||||||
'timestamp' => $message->created_at->format(__('Y-m-d H:i')),
|
|
||||||
'from' => User_Nick_render($sender_user_source),
|
|
||||||
'to' => User_Nick_render($receiver_user_source),
|
|
||||||
'text' => nl2br(htmlspecialchars($message->text))
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($message->receiver_id == $user->id) {
|
|
||||||
if (!$message->read) {
|
|
||||||
$messages_table_entry['actions'] = button(
|
|
||||||
page_link_to('user_messages', ['action' => 'read', 'id' => $message->id]),
|
|
||||||
__('mark as read'),
|
|
||||||
'btn-sm'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$messages_table_entry['actions'] = button(
|
|
||||||
page_link_to('user_messages', ['action' => 'delete', 'id' => $message->id]),
|
|
||||||
__('delete message'),
|
|
||||||
'btn-sm'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$messages_table[] = $messages_table_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
return page_with_title(messages_title(), [
|
|
||||||
msg(),
|
|
||||||
sprintf(__('Hello %s, here can you leave messages for other angels'), User_Nick_render($user)),
|
|
||||||
form([
|
|
||||||
table([
|
|
||||||
'new' => __('New'),
|
|
||||||
'timestamp' => __('Date'),
|
|
||||||
'from' => __('Transmitted'),
|
|
||||||
'to' => __('Recipient'),
|
|
||||||
'text' => __('Message'),
|
|
||||||
'actions' => ''
|
|
||||||
], $messages_table)
|
|
||||||
], page_link_to('user_messages', ['action' => 'send']))
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
switch ($request->input('action')) {
|
|
||||||
case 'read':
|
|
||||||
if ($request->has('id') && preg_match('/^\d{1,11}$/', $request->input('id'))) {
|
|
||||||
$message_id = $request->input('id');
|
|
||||||
} else {
|
|
||||||
return error(__('Incomplete call, missing Message ID.'), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = Message::find($message_id);
|
|
||||||
if ($message !== null && $message->receiver_id == $user->id) {
|
|
||||||
$message->read = true;
|
|
||||||
$message->save();
|
|
||||||
throw_redirect(page_link_to('user_messages'));
|
|
||||||
} else {
|
|
||||||
return error(__('No Message found.'), true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'delete':
|
|
||||||
if ($request->has('id') && preg_match('/^\d{1,11}$/', $request->input('id'))) {
|
|
||||||
$message_id = $request->input('id');
|
|
||||||
} else {
|
|
||||||
return error(__('Incomplete call, missing Message ID.'), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = Message::find($message_id);
|
|
||||||
if ($message !== null && $message->user_id == $user->id) {
|
|
||||||
$message->delete();
|
|
||||||
throw_redirect(page_link_to('user_messages'));
|
|
||||||
} else {
|
|
||||||
return error(__('No Message found.'), true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'send':
|
|
||||||
$receiver = User::find($request->input('to'));
|
|
||||||
$text = $request->input('text');
|
|
||||||
|
|
||||||
if ($receiver !== null && !empty($text)) {
|
|
||||||
Message::create([
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'receiver_id' => $request->input('to'),
|
|
||||||
'text' => $request->input('text')
|
|
||||||
]);
|
|
||||||
throw_redirect(page_link_to('user_messages'));
|
|
||||||
} else {
|
|
||||||
return error(__('Transmitting was terminated with an Error.'), true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return error(__('Wrong action.'), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,3 +348,14 @@ code {
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation {
|
||||||
|
height: 60vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
max-width: 75%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
|
@ -154,3 +154,6 @@ msgstr "Eine Beschreibung findest du unter %2$s"
|
||||||
|
|
||||||
msgid "user.edit.success"
|
msgid "user.edit.success"
|
||||||
msgstr "Benutzer erfolgreich bearbeitet."
|
msgstr "Benutzer erfolgreich bearbeitet."
|
||||||
|
|
||||||
|
msgid "messages.delete.success"
|
||||||
|
msgstr "Nachricht erfolgreich gelöscht."
|
||||||
|
|
|
@ -1308,10 +1308,6 @@ msgstr "Zu löschende Schichten"
|
||||||
msgid "It's done!"
|
msgid "It's done!"
|
||||||
msgstr "Erledigt!"
|
msgstr "Erledigt!"
|
||||||
|
|
||||||
#: includes/pages/user_messages.php:121
|
|
||||||
msgid "Message"
|
|
||||||
msgstr "Nachricht"
|
|
||||||
|
|
||||||
#: includes/pages/admin_rooms.php:202
|
#: includes/pages/admin_rooms.php:202
|
||||||
#: includes/view/User_view.php:129
|
#: includes/view/User_view.php:129
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
|
@ -1685,10 +1681,6 @@ msgstr "Nachname"
|
||||||
msgid "Entry required!"
|
msgid "Entry required!"
|
||||||
msgstr "Pflichtfeld!"
|
msgstr "Pflichtfeld!"
|
||||||
|
|
||||||
#: includes/pages/user_messages.php:11
|
|
||||||
msgid "Messages"
|
|
||||||
msgstr "Nachrichten"
|
|
||||||
|
|
||||||
#: includes/pages/user_messages.php:49
|
#: includes/pages/user_messages.php:49
|
||||||
msgid "Select recipient..."
|
msgid "Select recipient..."
|
||||||
msgstr "Empfänger auswählen..."
|
msgstr "Empfänger auswählen..."
|
||||||
|
@ -3038,3 +3030,18 @@ msgstr "Angekommen"
|
||||||
|
|
||||||
msgid "user.got_shirt"
|
msgid "user.got_shirt"
|
||||||
msgstr "Shirt bekommen"
|
msgstr "Shirt bekommen"
|
||||||
|
|
||||||
|
msgid "messages.title"
|
||||||
|
msgstr "Nachrichten"
|
||||||
|
|
||||||
|
msgid "messages.choose.an.angel"
|
||||||
|
msgstr "Wähle einen Engel"
|
||||||
|
|
||||||
|
msgid "messages.to.conversation"
|
||||||
|
msgstr "Zur Konversation"
|
||||||
|
|
||||||
|
msgid "angel"
|
||||||
|
msgstr "Engel"
|
||||||
|
|
||||||
|
msgid "message"
|
||||||
|
msgstr "Nachricht"
|
||||||
|
|
|
@ -152,3 +152,6 @@ msgstr "You can find a description at %2$s"
|
||||||
|
|
||||||
msgid "user.edit.success"
|
msgid "user.edit.success"
|
||||||
msgstr "User edited successfully."
|
msgstr "User edited successfully."
|
||||||
|
|
||||||
|
msgid "messages.delete.success"
|
||||||
|
msgstr "Message successfully deleted."
|
||||||
|
|
|
@ -300,3 +300,18 @@ msgstr "Arrived"
|
||||||
|
|
||||||
msgid "user.got_shirt"
|
msgid "user.got_shirt"
|
||||||
msgstr "Got shirt"
|
msgstr "Got shirt"
|
||||||
|
|
||||||
|
msgid "messages.title"
|
||||||
|
msgstr "Messages"
|
||||||
|
|
||||||
|
msgid "messages.choose.an.angel"
|
||||||
|
msgstr "Choose an Angel"
|
||||||
|
|
||||||
|
msgid "messages.to.conversation"
|
||||||
|
msgstr "To Conversation"
|
||||||
|
|
||||||
|
msgid "angel"
|
||||||
|
msgstr "Angel"
|
||||||
|
|
||||||
|
msgid "message"
|
||||||
|
msgstr "Message"
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_user() and has_permission_to('user_messages') %}
|
{% if is_user() and has_permission_to('user_messages') %}
|
||||||
{{ _self.toolbar_item(menuUserMessages(), url('user-messages'), 'user-messages', 'envelope') }}
|
{{ _self.toolbar_item(menuUserMessages(), url('messages'), 'messages', 'envelope') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ menuUserHints() }}
|
{{ menuUserHints() }}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
{% extends "layouts/app.twig" %}
|
||||||
|
{% import 'macros/base.twig' as m %}
|
||||||
|
{% import 'macros/form.twig' as f %}
|
||||||
|
|
||||||
|
{% block title %}{{ __('messages.title') }}: {{ other_user.nameWithPronoun() }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>
|
||||||
|
{{ __('messages.title') }}: <span class="icon-icon_angel"></span> {{ other_user.nameWithPronoun() }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 offset-lg-2">
|
||||||
|
<div class="row conversation">
|
||||||
|
{% for msg in messages %}
|
||||||
|
{% if msg.user_id == other_user.id %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div>
|
||||||
|
<div class="message alert alert-secondary position-relative">
|
||||||
|
<div>{{ msg.text | nl2br }}</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<small class="opacity-75">{{ msg.created_at }}</small>
|
||||||
|
</div>
|
||||||
|
{% if msg.read == false %}
|
||||||
|
<span class="position-absolute top-0 start-100 translate-middle-x p-2 bg-danger rounded-circle">
|
||||||
|
<span class="visually-hidden">New alerts</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<div class="message alert alert-primary">
|
||||||
|
<div>{{ msg.text | nl2br }}</div>
|
||||||
|
<div class="text-end">
|
||||||
|
|
||||||
|
<form action="{{ url('/messages/' ~ other_user.id ~ '/' ~ msg.id) }}"
|
||||||
|
enctype="multipart/form-data" method="post">
|
||||||
|
{{ csrf() }}
|
||||||
|
<small class="opacity-75">{{ msg.created_at }}</small>
|
||||||
|
{{ f.submit(m.icon('trash'), {'btn_type': 'primary', 'size': 'sm'}) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="" enctype="multipart/form-data" method="post">
|
||||||
|
{{ csrf() }}
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<textarea class="form-control" id="text" name="text" required="" rows="1"></textarea>
|
||||||
|
{{ f.submit(m.icon('send-fill')) }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,64 @@
|
||||||
|
{% extends "layouts/app.twig" %}
|
||||||
|
{% import 'macros/base.twig' as m %}
|
||||||
|
{% import 'macros/form.twig' as f %}
|
||||||
|
|
||||||
|
{% block title %}{{ __('messages.title') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>
|
||||||
|
{{ block('title') }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<form action="" enctype="multipart/form-data" method="post">
|
||||||
|
{{ csrf() }}
|
||||||
|
<div class="row gx-2 mb-3">
|
||||||
|
<div class="col-auto">
|
||||||
|
<select id="user_id" name="user_id" class="form-control pe-5" required>
|
||||||
|
<option value="">{{ __('messages.choose.an.angel') }}</option>
|
||||||
|
{% for value,decription in users -%}
|
||||||
|
<option value="{{ value }}">{{ decription }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ f.submit(__('messages.to.conversation'), {'btn_type': 'secondary'}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table class="table table-striped data">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ __('angel') }}</th>
|
||||||
|
<th>{{ __('message') }}</th>
|
||||||
|
<th>{{ __('Date') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in conversations %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="icon-icon_angel"></span>
|
||||||
|
{{ c.other_user.nameWithPronoun() }}
|
||||||
|
{% if c.unread_messages > 0 %}
|
||||||
|
<span class="badge bg-danger">{{ c.unread_messages }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url('messages/' ~ c.other_user.id) }}">
|
||||||
|
{{ c.latest_message.text|length > 100 ? c.latest_message.text|slice(0, 100) ~ '...' : c.latest_message.text }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ c.latest_message.created_at }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,285 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -140,10 +140,6 @@ class LegacyMiddleware implements MiddlewareInterface
|
||||||
return [$title, $content];
|
return [$title, $content];
|
||||||
case 'user_worklog':
|
case 'user_worklog':
|
||||||
return user_worklog_controller();
|
return user_worklog_controller();
|
||||||
case 'user_messages':
|
|
||||||
$title = messages_title();
|
|
||||||
$content = user_messages();
|
|
||||||
return [$title, $content];
|
|
||||||
case 'user_settings':
|
case 'user_settings':
|
||||||
$title = settings_title();
|
$title = settings_title();
|
||||||
$content = user_settings();
|
$content = user_settings();
|
||||||
|
|
|
@ -20,8 +20,10 @@ use Illuminate\Support\Carbon;
|
||||||
* @property string $text
|
* @property string $text
|
||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
|
* @property-read User $sender
|
||||||
* @property-read User $receiver
|
* @property-read User $receiver
|
||||||
* @method static Builder|Message whereId($value)
|
* @method static Builder|Message whereId($value)
|
||||||
|
* @method static Builder|Message whereUserId($value)
|
||||||
* @method static Builder|Message whereReceiverId($value)
|
* @method static Builder|Message whereReceiverId($value)
|
||||||
* @method static Builder|Message whereRead($value)
|
* @method static Builder|Message whereRead($value)
|
||||||
* @method static Builder|Message whereText($value)
|
* @method static Builder|Message whereText($value)
|
||||||
|
@ -56,6 +58,14 @@ class Message extends BaseModel
|
||||||
'read' => false,
|
'read' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function sender(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'user_id');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsTo
|
* @return BelongsTo
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -225,4 +225,18 @@ class User extends BaseModel
|
||||||
->orderBy('read')
|
->orderBy('read')
|
||||||
->orderBy('id', 'DESC');
|
->orderBy('id', 'DESC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return either just the user name or the name alongside with the pronoun.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function nameWithPronoun(): string
|
||||||
|
{
|
||||||
|
if (config('enable_pronoun')) {
|
||||||
|
$pronoun = $this->personalData->pronoun;
|
||||||
|
return $pronoun ? $this->name . ' (' . $pronoun . ')' : $this->name;
|
||||||
|
} else {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,585 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Engelsystem\Controllers\MessagesController;
|
||||||
|
use Engelsystem\Helpers\Authenticator;
|
||||||
|
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||||
|
use Engelsystem\Http\Exceptions\ValidationException;
|
||||||
|
use Engelsystem\Http\UrlGenerator;
|
||||||
|
use Engelsystem\Http\UrlGeneratorInterface;
|
||||||
|
use Engelsystem\Http\Validation\Validator;
|
||||||
|
use Engelsystem\Models\Message;
|
||||||
|
use Engelsystem\Models\Question;
|
||||||
|
use Engelsystem\Models\User\PersonalData;
|
||||||
|
use Engelsystem\Models\User\Settings;
|
||||||
|
use Engelsystem\Models\User\User;
|
||||||
|
use Engelsystem\Test\Unit\HasDatabase;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
|
||||||
|
class MessagesControllerTest extends ControllerTest
|
||||||
|
{
|
||||||
|
use HasDatabase;
|
||||||
|
|
||||||
|
/** @var MessagesController */
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
/** @var Authenticator|MockObject */
|
||||||
|
protected $auth;
|
||||||
|
|
||||||
|
/** @var User */
|
||||||
|
protected $user_a;
|
||||||
|
/** @var User */
|
||||||
|
protected $user_b;
|
||||||
|
|
||||||
|
/** @var Carbon */
|
||||||
|
protected $now;
|
||||||
|
/** @var Carbon */
|
||||||
|
protected $one_minute_ago;
|
||||||
|
/** @var Carbon */
|
||||||
|
protected $two_minutes_ago;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: underNormalConditions -> returnsCorrectViewAndData
|
||||||
|
*/
|
||||||
|
public function testIndexUnderNormalConditionsReturnsCorrectViewAndData()
|
||||||
|
{
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$this->assertEquals('pages/messages/overview.twig', $view);
|
||||||
|
$this->assertArrayHasKey('conversations', $data);
|
||||||
|
$this->assertArrayHasKey('users', $data);
|
||||||
|
$this->assertArrayOrCollection($data['conversations']);
|
||||||
|
$this->assertArrayOrCollection($data['users']);
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: otherUsersExist -> returnsUsersWithoutMeOrderedByName
|
||||||
|
*/
|
||||||
|
public function testIndexOtherUsersExistReturnsUsersWithoutMeOrderedByName()
|
||||||
|
{
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$users = $data['users'];
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($users));
|
||||||
|
$this->assertEquals('b', $users[$this->user_b->id]);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: pronounsDeactivated -> userListHasNoPronouns
|
||||||
|
*/
|
||||||
|
public function testIndexPronounsDeactivatedUserListHasNoPronouns()
|
||||||
|
{
|
||||||
|
$this->user_with_pronoun = User::factory(['name' => 'x'])
|
||||||
|
->has(PersonalData::factory(['pronoun' => 'X']))->create();
|
||||||
|
$this->user_without_pronoun = $this->user_b;
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$users = $data['users'];
|
||||||
|
|
||||||
|
$this->assertEquals('x', $users[$this->user_with_pronoun->id]);
|
||||||
|
$this->assertEquals('b', $users[$this->user_without_pronoun->id]);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: pronounsActivated -> userListHasPronouns
|
||||||
|
*/
|
||||||
|
public function testIndexPronounsActivatedUserListHasPronouns()
|
||||||
|
{
|
||||||
|
config(['enable_pronoun' => true]);
|
||||||
|
|
||||||
|
$this->user_with_pronoun = User::factory(['name' => 'x'])
|
||||||
|
->has(PersonalData::factory(['pronoun' => 'X']))->create();
|
||||||
|
$this->user_without_pronoun = $this->user_b;
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$users = $data['users'];
|
||||||
|
|
||||||
|
$this->assertEquals('x (X)', $users[$this->user_with_pronoun->id]);
|
||||||
|
$this->assertEquals('b', $users[$this->user_without_pronoun->id]);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: withNoConversation -> returnsEmptyConversationList
|
||||||
|
*/
|
||||||
|
public function testIndexWithNoConversationReturnsEmptyConversationList()
|
||||||
|
{
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$this->assertEquals(0, count($data['conversations']));
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: withConversation -> conversationContainsCorrectData
|
||||||
|
*/
|
||||||
|
public function testIndexWithConversationConversationContainsCorrectData()
|
||||||
|
{
|
||||||
|
// save messages in wrong order to ensure latest message considers creation date, not id.
|
||||||
|
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->two_minutes_ago);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->one_minute_ago);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$conversations = $data['conversations'];
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($conversations));
|
||||||
|
$c = $conversations[0];
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('other_user', $c);
|
||||||
|
$this->assertArrayHasKey('latest_message', $c);
|
||||||
|
$this->assertArrayHasKey('unread_messages', $c);
|
||||||
|
|
||||||
|
$this->assertTrue($c['other_user'] instanceof User);
|
||||||
|
$this->assertTrue($c['latest_message'] instanceof Message);
|
||||||
|
$this->assertEquals('string', gettype($c['unread_messages']));
|
||||||
|
|
||||||
|
$this->assertEquals('b', $c['other_user']->name);
|
||||||
|
$this->assertEquals('b>a', $c['latest_message']->text);
|
||||||
|
$this->assertEquals(2, $c['unread_messages']);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: withConversations -> onlyContainsConversationsWithMe
|
||||||
|
*/
|
||||||
|
public function testIndexWithConversationsOnlyContainsConversationsWithMe()
|
||||||
|
{
|
||||||
|
$user_c = User::factory(['name' => 'c'])->create();
|
||||||
|
|
||||||
|
// save messages in wrong order to ensure latest message considers creation date, not id.
|
||||||
|
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->createMessage($this->user_b, $user_c, 'b>c', $this->now);
|
||||||
|
$this->createMessage($user_c, $this->user_a, 'c>a', $this->now);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$conversations = $data['conversations'];
|
||||||
|
|
||||||
|
$this->assertEquals(2, count($conversations));
|
||||||
|
$msg0 = $conversations[0]['latest_message']->text;
|
||||||
|
$msg1 = $conversations[1]['latest_message']->text;
|
||||||
|
$this->assertTrue(($msg0 == 'a>b' && $msg1 == 'c>a') || ($msg1 == 'c>a' && $msg0 == 'a>b'));
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox index: withConversations -> conversationsOrderedByDate
|
||||||
|
*/
|
||||||
|
public function testIndexWithConversationsConversationsOrderedByDate()
|
||||||
|
{
|
||||||
|
$user_c = User::factory(['name' => 'c'])->create();
|
||||||
|
$user_d = User::factory(['name' => 'd'])->create();
|
||||||
|
|
||||||
|
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->createMessage($user_d, $this->user_a, 'd>a', $this->two_minutes_ago);
|
||||||
|
$this->createMessage($this->user_a, $user_c, 'a>c', $this->one_minute_ago);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$conversations = $data['conversations'];
|
||||||
|
|
||||||
|
$this->assertEquals('a>b', $conversations[0]['latest_message']->text);
|
||||||
|
$this->assertEquals('a>c', $conversations[1]['latest_message']->text);
|
||||||
|
$this->assertEquals('d>a', $conversations[2]['latest_message']->text);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox ToConversation: withNoUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testToConversationWithNoUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
$this->controller->toConversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox ToConversation: withUserIdGiven -> redirect
|
||||||
|
*/
|
||||||
|
public function testToConversationWithUserIdGivenRedirect()
|
||||||
|
{
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'user_id' => '1',
|
||||||
|
]);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('redirectTo')
|
||||||
|
->with('http://localhost/messages/1')
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
$this->controller->toConversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withNoUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testConversationWithNoUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->expectException(ModelNotFoundException::class);
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withMyUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testConversationWithMyUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_a->id);
|
||||||
|
$this->expectException(HttpForbidden::class);
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withUnknownUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testConversationWithUnknownUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', '1234');
|
||||||
|
$this->expectException(ModelNotFoundException::class);
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: underNormalConditions -> returnsCorrectViewAndData
|
||||||
|
*/
|
||||||
|
public function testConversationUnderNormalConditionsReturnsCorrectViewAndData()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$this->assertEquals('pages/messages/conversation.twig', $view);
|
||||||
|
$this->assertArrayHasKey('messages', $data);
|
||||||
|
$this->assertArrayHasKey('other_user', $data);
|
||||||
|
$this->assertArrayOrCollection($data['messages']);
|
||||||
|
$this->assertTrue($data['other_user'] instanceof User);
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withNoMessages -> returnsEmptyMessageList
|
||||||
|
*/
|
||||||
|
public function testConversationWithNoMessagesReturnsEmptyMessageList()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$this->assertEquals(0, count($data['messages']));
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withMessages -> messagesOnlyWithThatUserOrderedByDate
|
||||||
|
*/
|
||||||
|
public function testConversationWithMessagesMessagesOnlyWithThatUserOrderedByDate()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
|
||||||
|
$user_c = User::factory(['name' => 'c'])->create();
|
||||||
|
|
||||||
|
// to be listed
|
||||||
|
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->two_minutes_ago);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a2', $this->one_minute_ago);
|
||||||
|
|
||||||
|
// not to be listed
|
||||||
|
$this->createMessage($this->user_a, $user_c, 'a>c', $this->now);
|
||||||
|
$this->createMessage($user_c, $this->user_b, 'b>c', $this->now);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$messages = $data['messages'];
|
||||||
|
$this->assertEquals(3, count($messages));
|
||||||
|
$this->assertEquals('b>a', $messages[0]->text);
|
||||||
|
$this->assertEquals('b>a2', $messages[1]->text);
|
||||||
|
$this->assertEquals('a>b', $messages[2]->text);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withUnreadMessages -> messagesToMeWillStillBeReturnedAsUnread
|
||||||
|
*/
|
||||||
|
public function testConversationWithUnreadMessagesMessagesToMeWillStillBeReturnedAsUnread()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->now);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
$this->assertFalse($data['messages'][0]->read);
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox conversation: withUnreadMessages -> messagesToMeWillBeMarkedAsRead
|
||||||
|
*/
|
||||||
|
public function testConversationWithUnreadMessagesMessagesToMeWillBeMarkedAsRead()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) {
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$msg = $this->createMessage($this->user_b, $this->user_a, 'b>a', $this->now);
|
||||||
|
$this->controller->conversation($this->request);
|
||||||
|
$this->assertTrue(Message::whereId($msg->id)->first()->read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox send: withNoTextGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testSendWithNoTextGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
$this->controller->send($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox send: withNoUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testSendWithNoUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'text' => 'a',
|
||||||
|
]);
|
||||||
|
$this->expectException(ModelNotFoundException::class);
|
||||||
|
$this->controller->send($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox send: withMyUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testSendWithMyUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'text' => 'a',
|
||||||
|
]);
|
||||||
|
$this->request->attributes->set('user_id', $this->user_a->id);
|
||||||
|
$this->expectException(HttpForbidden::class);
|
||||||
|
$this->controller->send($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox send: withUnknownUserIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testSendWithUnknownUserIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'text' => 'a',
|
||||||
|
]);
|
||||||
|
$this->request->attributes->set('user_id', '1234');
|
||||||
|
$this->expectException(ModelNotFoundException::class);
|
||||||
|
$this->controller->send($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox send: withUserAndTextGiven -> savesMessage
|
||||||
|
*/
|
||||||
|
public function testSendWithUserAndTextGivenSavesMessage()
|
||||||
|
{
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'text' => 'a',
|
||||||
|
]);
|
||||||
|
$this->request->attributes->set('user_id', $this->user_b->id);
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('redirectTo')
|
||||||
|
->with('http://localhost/messages/' . $this->user_b->id)
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
$this->controller->send($this->request);
|
||||||
|
|
||||||
|
$msg = Message::whereText('a')->first();
|
||||||
|
$this->assertEquals($this->user_a->id, $msg->user_id);
|
||||||
|
$this->assertEquals($this->user_b->id, $msg->receiver_id);
|
||||||
|
$this->assertFalse($msg->read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox delete: withNoMsgIdGiven -> throwsException
|
||||||
|
*/
|
||||||
|
public function testDeleteWithNoMsgIdGivenThrowsException()
|
||||||
|
{
|
||||||
|
$this->expectException(ModelNotFoundException::class);
|
||||||
|
$this->controller->delete($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox delete: tryingToDeleteSomeonesMessage -> throwsException
|
||||||
|
*/
|
||||||
|
public function testDeleteTryingToDeleteSomeonesMessageThrowsException()
|
||||||
|
{
|
||||||
|
$this->expectException(HttpForbidden::class);
|
||||||
|
|
||||||
|
$msg = $this->createMessage($this->user_b, $this->user_a, 'a>b', $this->now);
|
||||||
|
$this->request->attributes->set('msg_id', $msg->id);
|
||||||
|
|
||||||
|
$this->controller->delete($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox delete: tryingToDeleteMyMessage -> deletesItAndRedirect
|
||||||
|
*/
|
||||||
|
public function testDeleteTryingToDeleteMyMessageDeletesItAndRedirect()
|
||||||
|
{
|
||||||
|
$msg = $this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->request->attributes->set('msg_id', $msg->id);
|
||||||
|
$this->request->attributes->set('user_id', '1');
|
||||||
|
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('redirectTo')
|
||||||
|
->with('http://localhost/messages/1')
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
$this->controller->delete($this->request);
|
||||||
|
|
||||||
|
$this->assertEquals(0, count(Message::whereId($msg->id)->get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox NumberOfUnreadMessages: withNoMessages -> returns0
|
||||||
|
*/
|
||||||
|
public function testNumberOfUnreadMessagesWithNoMessagesReturns0()
|
||||||
|
{
|
||||||
|
$this->assertEquals(0, $this->controller->numberOfUnreadMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox NumberOfUnreadMessages: withMessagesNotToMe -> messagesNotToMeAreIgnored
|
||||||
|
*/
|
||||||
|
public function testNumberOfUnreadMessagesWithMessagesNotToMeMessagesNotToMeAreIgnored()
|
||||||
|
{
|
||||||
|
$user_c = User::factory(['name' => 'c'])->create();
|
||||||
|
|
||||||
|
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
|
||||||
|
$this->createMessage($this->user_b, $user_c, 'b>c', $this->now);
|
||||||
|
$this->assertEquals(0, $this->controller->numberOfUnreadMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox NumberOfUnreadMessages: withMessages -> returnsSumOfUnreadMessagesSentToMe
|
||||||
|
*/
|
||||||
|
public function testNumberOfUnreadMessagesWithMessagesReturnsSumOfUnreadMessagesSentToMe()
|
||||||
|
{
|
||||||
|
$user_c = User::factory(['name' => 'c'])->create();
|
||||||
|
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a1', $this->now);
|
||||||
|
$this->createMessage($this->user_b, $this->user_a, 'b>a2', $this->now);
|
||||||
|
$this->createMessage($user_c, $this->user_a, 'c>a', $this->now);
|
||||||
|
|
||||||
|
$this->assertEquals(3, $this->controller->numberOfUnreadMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup environment
|
||||||
|
*/
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->auth = $this->createMock(Authenticator::class);
|
||||||
|
$this->app->instance(Authenticator::class, $this->auth);
|
||||||
|
|
||||||
|
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
|
||||||
|
|
||||||
|
$this->user_a = User::factory(['name' => 'a'])->create();
|
||||||
|
$this->user_b = User::factory(['name' => 'b'])->create();
|
||||||
|
$this->setExpects($this->auth, 'user', null, $this->user_a, $this->any());
|
||||||
|
|
||||||
|
$this->now = Carbon::now();
|
||||||
|
$this->one_minute_ago = Carbon::now()->subMinute();
|
||||||
|
$this->two_minutes_ago = Carbon::now()->subMinutes(2);
|
||||||
|
|
||||||
|
$this->controller = $this->app->get(MessagesController::class);
|
||||||
|
$this->controller->setValidator(new Validator());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertArrayOrCollection($obj)
|
||||||
|
{
|
||||||
|
$this->assertTrue(gettype($obj) == 'array' || $obj instanceof Collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createMessage(User $from, User $to, string $text, Carbon $at): Message
|
||||||
|
{
|
||||||
|
Message::unguard(); // unguard temporarily to save custom creation dates.
|
||||||
|
$msg = new Message([
|
||||||
|
'user_id' => $from->id,
|
||||||
|
'receiver_id' => $to->id,
|
||||||
|
'text' => $text,
|
||||||
|
'created_at' => $at,
|
||||||
|
'updated_at' => $at,
|
||||||
|
]);
|
||||||
|
$msg->save();
|
||||||
|
Message::reguard();
|
||||||
|
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Models\User;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
||||||
|
use Engelsystem\Config\Config;
|
||||||
use Engelsystem\Models\BaseModel;
|
use Engelsystem\Models\BaseModel;
|
||||||
use Engelsystem\Models\News;
|
use Engelsystem\Models\News;
|
||||||
use Engelsystem\Models\NewsComment;
|
use Engelsystem\Models\NewsComment;
|
||||||
|
@ -276,4 +277,45 @@ class UserTest extends ModelTest
|
||||||
$this->assertContains($question1->id, $answers);
|
$this->assertContains($question1->id, $answers);
|
||||||
$this->assertContains($question2->id, $answers);
|
$this->assertContains($question2->id, $answers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox nameWithPronoun: pronounsDeactivated -> returnNameOnly
|
||||||
|
*/
|
||||||
|
public function testNameWithPronounPronounsDeactivatedReturnNameOnly()
|
||||||
|
{
|
||||||
|
$user_with_pronoun = User::factory(['name' => 'x'])
|
||||||
|
->has(PersonalData::factory(['pronoun' => 'X']))->create();
|
||||||
|
$user_without_pronoun = User::factory(['name' => 'y'])->create();
|
||||||
|
|
||||||
|
$this->assertEquals('x', $user_with_pronoun->nameWithPronoun());
|
||||||
|
$this->assertEquals('y', $user_without_pronoun->nameWithPronoun());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @testdox nameWithPronoun: pronounsActivated -> returnNameAndPronoun
|
||||||
|
*/
|
||||||
|
public function testNameWithPronounPronounsActivatedReturnNameAndPronoun()
|
||||||
|
{
|
||||||
|
config(['enable_pronoun' => true]);
|
||||||
|
|
||||||
|
$user_with_pronoun = User::factory(['name' => 'x'])
|
||||||
|
->has(PersonalData::factory(['pronoun' => 'X']))->create();
|
||||||
|
$user_without_pronoun = User::factory(['name' => 'y'])->create();
|
||||||
|
|
||||||
|
$this->assertEquals('x (X)', $user_with_pronoun->nameWithPronoun());
|
||||||
|
$this->assertEquals('y', $user_without_pronoun->nameWithPronoun());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare test
|
||||||
|
*/
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// config needed for checking if pronouns are activated.
|
||||||
|
$config = new Config();
|
||||||
|
$this->app->instance('config', $config);
|
||||||
|
$this->app->instance(Config::class, $config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue