Unified user notifications

This commit is contained in:
Igor Scheller 2023-02-02 22:53:51 +01:00
parent 1fe30fc82f
commit 713f8222e4
43 changed files with 313 additions and 213 deletions

View File

@ -88,7 +88,7 @@ function angeltype_delete_controller()
if (request()->hasPostData('delete')) { if (request()->hasPostData('delete')) {
$angeltype->delete(); $angeltype->delete();
engelsystem_log('Deleted angeltype: ' . AngelType_name_render($angeltype, true)); engelsystem_log('Deleted angeltype: ' . AngelType_name_render($angeltype, true));
success(sprintf(__('Angeltype %s deleted.'), AngelType_name_render($angeltype))); success(sprintf(__('Angeltype %s deleted.'), $angeltype->name));
throw_redirect(page_link_to('angeltypes')); throw_redirect(page_link_to('angeltypes'));
} }

View File

@ -108,7 +108,7 @@ function shift_entry_create_controller_admin(Shift $shift, ?AngelType $angeltype
$shiftEntry->save(); $shiftEntry->save();
ShiftEntry_onCreate($shiftEntry); ShiftEntry_onCreate($shiftEntry);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user))); success(sprintf(__('%s has been subscribed to the shift.'), $signup_user->name));
throw_redirect(shift_link($shift)); throw_redirect(shift_link($shift));
} }
@ -157,7 +157,7 @@ function shift_entry_create_controller_supporter(Shift $shift, AngelType $angelt
$shiftEntry->save(); $shiftEntry->save();
ShiftEntry_onCreate($shiftEntry); ShiftEntry_onCreate($shiftEntry);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user))); success(sprintf(__('%s has been subscribed to the shift.'), $signup_user->name));
throw_redirect(shift_link($shift)); throw_redirect(shift_link($shift));
} }

View File

@ -82,7 +82,7 @@ function user_angeltypes_delete_all_controller(): array
->delete(); ->delete();
engelsystem_log(sprintf('Denied all users for angeltype %s', AngelType_name_render($angeltype, true))); engelsystem_log(sprintf('Denied all users for angeltype %s', AngelType_name_render($angeltype, true)));
success(sprintf(__('Denied all users for angeltype %s.'), AngelType_name_render($angeltype))); success(sprintf(__('Denied all users for angeltype %s.'), $angeltype->name));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])); throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
} }
@ -121,7 +121,7 @@ function user_angeltypes_confirm_all_controller(): array
->update(['confirm_user_id' => $user->id]); ->update(['confirm_user_id' => $user->id]);
engelsystem_log(sprintf('Confirmed all users for angeltype %s', AngelType_name_render($angeltype, true))); engelsystem_log(sprintf('Confirmed all users for angeltype %s', AngelType_name_render($angeltype, true)));
success(sprintf(__('Confirmed all users for angeltype %s.'), AngelType_name_render($angeltype))); success(sprintf(__('Confirmed all users for angeltype %s.'), $angeltype->name));
foreach ($users as $user) { foreach ($users as $user) {
user_angeltype_confirm_email($user, $angeltype); user_angeltype_confirm_email($user, $angeltype);
@ -169,11 +169,7 @@ function user_angeltype_confirm_controller(): array
User_Nick_render($user_source, true), User_Nick_render($user_source, true),
AngelType_name_render($angeltype, true) AngelType_name_render($angeltype, true)
)); ));
success(sprintf( success(sprintf(__('%s confirmed for angeltype %s.'), $user_source->name, $angeltype->name));
__('%s confirmed for angeltype %s.'),
User_Nick_render($user_source),
AngelType_name_render($angeltype)
));
user_angeltype_confirm_email($user_source, $angeltype); user_angeltype_confirm_email($user_source, $angeltype);
@ -268,7 +264,7 @@ function user_angeltype_delete_controller(): array
$user_angeltype->delete(); $user_angeltype->delete();
engelsystem_log(sprintf('User %s removed from %s.', User_Nick_render($user_source, true), $angeltype->name)); engelsystem_log(sprintf('User %s removed from %s.', User_Nick_render($user_source, true), $angeltype->name));
success(sprintf(__('User %s removed from %s.'), User_Nick_render($user_source), $angeltype->name)); success(sprintf(__('User %s removed from %s.'), $user_source->name, $angeltype->name));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])); throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
} }
@ -323,11 +319,7 @@ function user_angeltype_update_controller(): array
AngelType_name_render($angeltype, true), AngelType_name_render($angeltype, true),
User_Nick_render($user_source, true) User_Nick_render($user_source, true)
)); ));
success(sprintf( success(sprintf($msg, $angeltype->name, $user_source->name));
$msg,
AngelType_name_render($angeltype),
User_Nick_render($user_source)
));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id])); throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]));
} }
@ -375,11 +367,7 @@ function user_angeltype_add_controller(): array
User_Nick_render($user_source, true), User_Nick_render($user_source, true),
AngelType_name_render($angeltype, true) AngelType_name_render($angeltype, true)
)); ));
success(sprintf( success(sprintf(__('User %s added to %s.'), $user_source->name, $angeltype->name));
__('User %s added to %s.'),
User_Nick_render($user_source),
AngelType_name_render($angeltype)
));
if ($request->hasPostData('auto_confirm_user')) { if ($request->hasPostData('auto_confirm_user')) {
$userAngelType->confirmUser()->associate($user_source); $userAngelType->confirmUser()->associate($user_source);

View File

@ -48,7 +48,7 @@ function engelsystem_email_to_user($recipientUser, $title, $message, $notIfItsMe
$translator->setLocale($locale); $translator->setLocale($locale);
if (!$status) { if (!$status) {
error(sprintf(__('User %s could not be notified by email due to an error.'), User_Nick_render($recipientUser))); error(sprintf(__('User %s could not be notified by email due to an error.'), $recipientUser->name));
engelsystem_log(sprintf('User %s could not be notified by email due to an error.', $recipientUser->name)); engelsystem_log(sprintf('User %s could not be notified by email due to an error.', $recipientUser->name));
} }

View File

@ -1,40 +1,15 @@
<?php <?php
use Engelsystem\Controllers\NotificationType;
/** /**
* Returns messages from session and removes them from the stack * Returns messages from session and removes them from the stack by rendering the messages twig template
* @param bool $includeMessagesFromNewProcedure
* If set, the messages from the new procedure are also included.
* The output will be similar to how it would be with messages.twig.
* @see \Engelsystem\Controllers\HasUserNotifications
* @return string * @return string
* @see \Engelsystem\Controllers\HasUserNotifications
*/ */
function msg(bool $includeMessagesFromNewProcedure = false) function msg()
{ {
$session = session(); return view('layouts/parts/messages.twig');
$message = $session->get('msg', '');
$session->set('msg', '');
if ($includeMessagesFromNewProcedure) {
foreach (session()->get('errors', []) as $msg) {
$message .= error(__($msg), true);
}
foreach (session()->get('warnings', []) as $msg) {
$message .= warning(__($msg), true);
}
foreach (session()->get('information', []) as $msg) {
$message .= info(__($msg), true);
}
foreach (session()->get('messages', []) as $msg) {
$message .= success(__($msg), true);
}
foreach (['errors', 'warnings', 'information', 'messages'] as $type) {
session()->remove($type);
}
}
return $message;
} }
/** /**
@ -46,7 +21,7 @@ function msg(bool $includeMessagesFromNewProcedure = false)
*/ */
function info($msg, $immediately = false) function info($msg, $immediately = false)
{ {
return alert('info', $msg, $immediately); return alert(NotificationType::INFORMATION, $msg, $immediately);
} }
/** /**
@ -58,7 +33,7 @@ function info($msg, $immediately = false)
*/ */
function warning($msg, $immediately = false) function warning($msg, $immediately = false)
{ {
return alert('warning', $msg, $immediately); return alert(NotificationType::WARNING, $msg, $immediately);
} }
/** /**
@ -70,7 +45,7 @@ function warning($msg, $immediately = false)
*/ */
function error($msg, $immediately = false) function error($msg, $immediately = false)
{ {
return alert('danger', $msg, $immediately); return alert(NotificationType::ERROR, $msg, $immediately);
} }
/** /**
@ -82,31 +57,44 @@ function error($msg, $immediately = false)
*/ */
function success($msg, $immediately = false) function success($msg, $immediately = false)
{ {
return alert('success', $msg, $immediately); return alert(NotificationType::MESSAGE, $msg, $immediately);
} }
/** /**
* Renders an alert message with the given alert-* class. * Renders an alert message with the given alert-* class or sets it in session
* *
* @param string $class * @see \Engelsystem\Controllers\HasUserNotifications
*
* @param NotificationType $type
* @param string $msg * @param string $msg
* @param bool $immediately * @param bool $immediately
* @return string * @return string
*/ */
function alert($class, $msg, $immediately = false) function alert(NotificationType $type, $msg, $immediately = false)
{ {
if (empty($msg)) { if (empty($msg)) {
return ''; return '';
} }
if ($immediately) { if ($immediately) {
return '<div class="alert alert-' . $class . '" role="alert">' . $msg . '</div>'; $type = str_replace(
[
NotificationType::ERROR->value,
NotificationType::WARNING->value,
NotificationType::INFORMATION->value,
NotificationType::MESSAGE->value,
],
['danger', 'warning', 'info', 'success'],
$type->value
);
return '<div class="alert alert-' . $type . '" role="alert">' . $msg . '</div>';
} }
$type = 'messages.' . $type->value;
$session = session(); $session = session();
$message = $session->get('msg', ''); $messages = $session->get($type, []);
$message .= alert($class, $msg, true); $messages[] = $msg;
$session->set('msg', $message); $session->set($type, $messages);
return ''; return '';
} }

View File

@ -50,6 +50,7 @@ function admin_shifts()
} }
// Load angeltypes // Load angeltypes
/** @var AngelType[] $types */
$types = AngelType::all(); $types = AngelType::all();
$needed_angel_types = []; $needed_angel_types = [];
foreach ($types as $type) { foreach ($types as $type) {

View File

@ -121,7 +121,7 @@ function guest_register()
} }
if (User::whereName($nick)->count() > 0) { if (User::whereName($nick)->count() > 0) {
$valid = false; $valid = false;
$msg .= error(sprintf(__('Your nick &quot;%s&quot; already exists.'), $nick), true); $msg .= error(sprintf(__('Your nick "%s" already exists.'), htmlspecialchars($nick)), true);
} }
} else { } else {
$valid = false; $valid = false;
@ -330,8 +330,8 @@ function guest_register()
} }
// If a welcome message is present, display it on the next page // If a welcome message is present, display it on the next page
if ($message = $config->get('welcome_msg')) { if ($config->get('welcome_msg')) {
info((new Parsedown())->text($message)); $session->set('show_welcome', true);
} }
// Login the user // Login the user

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Engelsystem\Controllers\Admin\Schedule; namespace Engelsystem\Controllers\Admin\Schedule;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Helpers\Carbon; use Engelsystem\Helpers\Carbon;
use DateTimeInterface; use DateTimeInterface;
use Engelsystem\Controllers\BaseController; use Engelsystem\Controllers\BaseController;
@ -83,7 +84,7 @@ class ImportSchedule extends BaseController
[ [
'is_index' => true, 'is_index' => true,
'schedules' => ScheduleUrl::all(), 'schedules' => ScheduleUrl::all(),
] + $this->getNotifications() ]
); );
} }
@ -98,7 +99,7 @@ class ImportSchedule extends BaseController
[ [
'schedule' => $schedule, 'schedule' => $schedule,
'shift_types' => ShiftType::all()->pluck('name', 'id'), 'shift_types' => ShiftType::all()->pluck('name', 'id'),
] + $this->getNotifications() ]
); );
} }
@ -169,7 +170,7 @@ class ImportSchedule extends BaseController
$schedule $schedule
) = $this->getScheduleData($request); ) = $this->getScheduleData($request);
} catch (ErrorException $e) { } catch (ErrorException $e) {
$this->addNotification($e->getMessage(), 'errors'); $this->addNotification($e->getMessage(), NotificationType::ERROR);
return back(); return back();
} }
@ -186,7 +187,7 @@ class ImportSchedule extends BaseController
'update' => $changeEvents, 'update' => $changeEvents,
'delete' => $deleteEvents, 'delete' => $deleteEvents,
], ],
] + $this->getNotifications() ]
); );
} }
@ -210,7 +211,7 @@ class ImportSchedule extends BaseController
$scheduleUrl $scheduleUrl
) = $this->getScheduleData($request); ) = $this->getScheduleData($request);
} catch (ErrorException $e) { } catch (ErrorException $e) {
$this->addNotification($e->getMessage(), 'errors'); $this->addNotification($e->getMessage(), NotificationType::ERROR);
return back(); return back();
} }
@ -250,8 +251,8 @@ class ImportSchedule extends BaseController
$scheduleUrl->touch(); $scheduleUrl->touch();
$this->log('Ended schedule "{name}" import', ['name' => $scheduleUrl->name]); $this->log('Ended schedule "{name}" import', ['name' => $scheduleUrl->name]);
return redirect($this->url, 303) $this->addNotification('schedule.import.success');
->with('messages', ['schedule.import.success']); return redirect($this->url, 303);
} }
protected function createRoom(Room $room): void protected function createRoom(Room $room): void

View File

@ -126,7 +126,7 @@ function ShiftEntry_create_view_supporter(Shift $shift, Room $room, AngelType $a
Shift_view_header($shift, $room), Shift_view_header($shift, $room),
info(sprintf( info(sprintf(
__('Do you want to sign up the following user for this shift as %s?'), __('Do you want to sign up the following user for this shift as %s?'),
AngelType_name_render($angeltype) $angeltype->name
), true), ), true),
form([ form([
form_select('user_id', __('User'), $users_select, $signup_user->id), form_select('user_id', __('User'), $users_select, $signup_user->id),
@ -153,7 +153,7 @@ function ShiftEntry_create_view_user(Shift $shift, Room $room, AngelType $angelt
. ' <small title="' . $start . '" data-countdown-ts="' . $shift->start->timestamp . '">%c</small>', . ' <small title="' . $start . '" data-countdown-ts="' . $shift->start->timestamp . '">%c</small>',
[ [
Shift_view_header($shift, $room), Shift_view_header($shift, $room),
info(sprintf(__('Do you want to sign up for this shift as %s?'), AngelType_name_render($angeltype)), true), info(sprintf(__('Do you want to sign up for this shift as %s?'), $angeltype->name), true),
form([ form([
form_textarea('comment', __('Comment (for your eyes only):'), $comment), form_textarea('comment', __('Comment (for your eyes only):'), $comment),
form_submit('submit', icon('check-lg') . __('Save')), form_submit('submit', icon('check-lg') . __('Save')),

View File

@ -20,7 +20,7 @@ function UserAngelType_update_view(UserAngelType $user_angeltype, User $user, An
? __('Do you really want to add supporter rights for %s to %s?') ? __('Do you really want to add supporter rights for %s to %s?')
: __('Do you really want to remove supporter rights for %s from %s?'), : __('Do you really want to remove supporter rights for %s from %s?'),
$angeltype->name, $angeltype->name,
User_Nick_render($user) $user->name
), true), ), true),
form([ form([
buttons([ buttons([
@ -92,7 +92,7 @@ function UserAngelType_confirm_view(UserAngelType $user_angeltype, User $user, A
msg(), msg(),
info(sprintf( info(sprintf(
__('Do you really want to confirm %s for %s?'), __('Do you really want to confirm %s for %s?'),
User_Nick_render($user), $user->name,
$angeltype->name $angeltype->name
), true), ), true),
form([ form([
@ -116,7 +116,7 @@ function UserAngelType_delete_view(UserAngelType $user_angeltype, User $user, An
msg(), msg(),
info(sprintf( info(sprintf(
__('Do you really want to delete %s from %s?'), __('Do you really want to delete %s from %s?'),
User_Nick_render($user), $user->name,
$angeltype->name $angeltype->name
), true), ), true),
form([ form([
@ -170,7 +170,7 @@ function UserAngelType_join_view($user, AngelType $angeltype)
msg(), msg(),
info(sprintf( info(sprintf(
__('Do you really want to add %s to %s?'), __('Do you really want to add %s to %s?'),
User_Nick_render($user), $user->name,
$angeltype->name $angeltype->name
), true), ), true),
form([ form([

View File

@ -534,7 +534,7 @@ function User_view(
. htmlspecialchars($user_source->name) . htmlspecialchars($user_source->name)
. (config('enable_user_name') ? ' <small>' . $user_name . '</small>' : ''), . (config('enable_user_name') ? ' <small>' . $user_name . '</small>' : ''),
[ [
msg(true), msg(),
div('row', [ div('row', [
div('col-md-12', [ div('col-md-12', [
buttons([ buttons([

View File

@ -1545,8 +1545,8 @@ msgstr ""
#: includes/pages/guest_login.php:82 #: includes/pages/guest_login.php:82
#, php-format #, php-format
msgid "Your nick &quot;%s&quot; already exists." msgid "Your nick \"%s\" already exists."
msgstr "Der Nick &quot;%s&quot; existiert schon." msgstr "Der Nick \"%s\" existiert bereits."
#: includes/pages/guest_login.php:86 #: includes/pages/guest_login.php:86
msgid "Please enter a nickname." msgid "Please enter a nickname."

View File

@ -1296,8 +1296,8 @@ msgstr "Logout"
#: includes/pages/guest_login.php:56 #: includes/pages/guest_login.php:56
#, php-format #, php-format
msgid "Your nick &quot;%s&quot; already exists." msgid "Your nick \"%s\" already exists."
msgstr "Seu apelido &quot;%s&quot; já existe." msgstr "Seu apelido \"%s\" já existe."
#: includes/pages/guest_login.php:60 #: includes/pages/guest_login.php:60
#, php-format #, php-format

View File

@ -1,19 +1,17 @@
{% import 'macros/base.twig' as m %} {% import 'macros/base.twig' as m %}
{{ msg() }} {% for message in notifications('error') %}
{% for message in errors|default([]) %}
{{ m.alert(__(message), 'danger') }} {{ m.alert(__(message), 'danger') }}
{% endfor %} {% endfor %}
{% for message in warnings|default([]) %} {% for message in notifications('warning') %}
{{ m.alert(__(message), 'warning') }} {{ m.alert(__(message), 'warning') }}
{% endfor %} {% endfor %}
{% for message in information|default([]) %} {% for message in notifications('information') %}
{{ m.alert(__(message), 'info') }} {{ m.alert(__(message), 'info') }}
{% endfor %} {% endfor %}
{% for message in messages|default([]) %} {% for message in notifications('message') %}
{{ m.alert(__(message), 'success') }} {{ m.alert(__(message), 'success') }}
{% endfor %} {% endfor %}

View File

@ -34,6 +34,10 @@
<div class="card-body"> <div class="card-body">
{% include 'layouts/parts/messages.twig' %} {% include 'layouts/parts/messages.twig' %}
{% if session_get('show_welcome', false) %}
{{ m.alert(config('welcome_msg') | md, null, true) }}
{% endif %}
<form action="" enctype="multipart/form-data" method="post"> <form action="" enctype="multipart/form-data" method="post">
{{ csrf() }} {{ csrf() }}
<div class="mb-3"> <div class="mb-3">

View File

@ -83,7 +83,7 @@ class FaqController extends BaseController
{ {
return $this->response->withView( return $this->response->withView(
'pages/faq/edit.twig', 'pages/faq/edit.twig',
['faq' => $faq] + $this->getNotifications() ['faq' => $faq]
); );
} }
} }

View File

@ -49,7 +49,7 @@ class NewsController extends BaseController
'news' => $news, 'news' => $news,
'is_meeting' => $news ? $news->is_meeting : $isMeetingDefault, 'is_meeting' => $news ? $news->is_meeting : $isMeetingDefault,
'is_pinned' => $news ? $news->is_pinned : false, 'is_pinned' => $news ? $news->is_pinned : false,
] + $this->getNotifications(), ],
); );
} }

View File

@ -42,7 +42,7 @@ class QuestionsController extends BaseController
return $this->response->withView( return $this->response->withView(
'pages/questions/overview.twig', 'pages/questions/overview.twig',
['questions' => $questions, 'is_admin' => true] + $this->getNotifications() ['questions' => $questions, 'is_admin' => true]
); );
} }
@ -120,7 +120,7 @@ class QuestionsController extends BaseController
{ {
return $this->response->withView( return $this->response->withView(
'pages/questions/edit.twig', 'pages/questions/edit.twig',
['question' => $question, 'is_admin' => true] + $this->getNotifications() ['question' => $question, 'is_admin' => true]
); );
} }
} }

View File

@ -42,7 +42,7 @@ class UserShirtController extends BaseController
return $this->response->withView( return $this->response->withView(
'admin/user/edit-shirt.twig', 'admin/user/edit-shirt.twig',
['userdata' => $user] + $this->getNotifications() ['userdata' => $user]
); );
} }

View File

@ -142,7 +142,7 @@ class UserWorkLogController extends BaseController
'work_hours' => $work_hours, 'work_hours' => $work_hours,
'comment' => $comment, 'comment' => $comment,
'is_edit' => $is_edit, 'is_edit' => $is_edit,
] + $this->getNotifications() ]
); );
} }

View File

@ -39,10 +39,7 @@ class AuthController extends BaseController
protected function showLogin(): Response protected function showLogin(): Response
{ {
return $this->response->withView( return $this->response->withView('pages/login');
'pages/login',
$this->getNotifications()
);
} }
/** /**
@ -58,7 +55,7 @@ class AuthController extends BaseController
$user = $this->auth->authenticate($data['login'], $data['password']); $user = $this->auth->authenticate($data['login'], $data['password']);
if (!$user instanceof User) { if (!$user instanceof User) {
$this->addNotification('auth.not-found', 'errors'); $this->addNotification('auth.not-found', NotificationType::ERROR);
return $this->showLogin(); return $this->showLogin();
} }

View File

@ -32,7 +32,7 @@ class FaqController extends BaseController
return $this->response->withView( return $this->response->withView(
'pages/faq/overview.twig', 'pages/faq/overview.twig',
['text' => $text, 'items' => $faq] + $this->getNotifications() ['text' => $text, 'items' => $faq]
); );
} }
} }

View File

@ -4,25 +4,40 @@ declare(strict_types=1);
namespace Engelsystem\Controllers; namespace Engelsystem\Controllers;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
trait HasUserNotifications trait HasUserNotifications
{ {
protected function addNotification(string|array $value, string $type = 'messages'): void protected function addNotification(string|array $value, NotificationType $type = NotificationType::MESSAGE): void
{ {
$type = 'messages.' . $type->value;
session()->set( session()->set(
$type, $type,
array_merge(session()->get($type, []), [$value]) array_merge_recursive(session()->get($type, []), (array) $value)
); );
} }
protected function getNotifications(): array /**
* @param NotificationType[]|null $types
* @return array<string,Collection|array<string>>
*/
protected function getNotifications(array $types = null): array
{ {
$return = []; $return = [];
foreach (['errors', 'warnings', 'information', 'messages'] as $type) { $types = $types ?: [
$return[$type] = Collection::make(Arr::flatten(session()->get($type, []))); NotificationType::ERROR,
session()->remove($type); NotificationType::WARNING,
NotificationType::INFORMATION,
NotificationType::MESSAGE,
];
foreach ($types as $type) {
$type = $type->value;
$path = 'messages.' . $type;
$return[$type] = Collection::make(
session()->get($path, [])
)->flatten();
session()->remove($path);
} }
return $return; return $return;

View File

@ -157,8 +157,6 @@ class NewsController extends BaseController
*/ */
protected function renderView(string $page, array $data): Response protected function renderView(string $page, array $data): Response
{ {
$data += $this->getNotifications();
return $this->response->withView($page, $data); return $this->response->withView($page, $data);
} }
} }

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Controllers;
enum NotificationType: string
{
case ERROR = 'error';
case WARNING = 'warning';
case INFORMATION = 'information';
case MESSAGE = 'message';
}

View File

@ -88,7 +88,7 @@ class PasswordResetController extends BaseController
]); ]);
if ($data['password'] !== $data['password_confirmation']) { if ($data['password'] !== $data['password_confirmation']) {
$this->addNotification('validation.password.confirmed', 'errors'); $this->addNotification('validation.password.confirmed', NotificationType::ERROR);
return $this->showView('pages/password/reset-form'); return $this->showView('pages/password/reset-form');
} }
@ -101,10 +101,7 @@ class PasswordResetController extends BaseController
protected function showView(string $view = 'pages/password/reset', array $data = []): Response protected function showView(string $view = 'pages/password/reset', array $data = []): Response
{ {
return $this->response->withView( return $this->response->withView($view, $data);
$view,
array_merge_recursive($this->getNotifications(), $data)
);
} }
protected function requireToken(Request $request): PasswordReset protected function requireToken(Request $request): PasswordReset

View File

@ -40,7 +40,7 @@ class QuestionsController extends BaseController
return $this->response->withView( return $this->response->withView(
'pages/questions/overview.twig', 'pages/questions/overview.twig',
['questions' => $questions] + $this->getNotifications() ['questions' => $questions]
); );
} }
@ -48,7 +48,7 @@ class QuestionsController extends BaseController
{ {
return $this->response->withView( return $this->response->withView(
'pages/questions/edit.twig', 'pages/questions/edit.twig',
['question' => null] + $this->getNotifications() ['question' => null]
); );
} }

View File

@ -40,7 +40,7 @@ class SettingsController extends BaseController
[ [
'settings_menu' => $this->settingsMenu(), 'settings_menu' => $this->settingsMenu(),
'user' => $user, 'user' => $user,
] + $this->getNotifications() ]
); );
} }
@ -60,10 +60,10 @@ class SettingsController extends BaseController
if (config('enable_planned_arrival')) { if (config('enable_planned_arrival')) {
if (!$this->isArrivalDateValid($data['planned_arrival_date'], $data['planned_departure_date'])) { if (!$this->isArrivalDateValid($data['planned_arrival_date'], $data['planned_departure_date'])) {
$this->addNotification('settings.profile.planned_arrival_date.invalid', 'errors'); $this->addNotification('settings.profile.planned_arrival_date.invalid', NotificationType::ERROR);
return $this->redirect->to('/settings/profile'); return $this->redirect->to('/settings/profile');
} elseif (!$this->isDepartureDateValid($data['planned_arrival_date'], $data['planned_departure_date'])) { } elseif (!$this->isDepartureDateValid($data['planned_arrival_date'], $data['planned_departure_date'])) {
$this->addNotification('settings.profile.planned_departure_date.invalid', 'errors'); $this->addNotification('settings.profile.planned_departure_date.invalid', NotificationType::ERROR);
return $this->redirect->to('/settings/profile'); return $this->redirect->to('/settings/profile');
} else { } else {
$user->personalData->planned_arrival_date = $data['planned_arrival_date']; $user->personalData->planned_arrival_date = $data['planned_arrival_date'];
@ -115,7 +115,7 @@ class SettingsController extends BaseController
[ [
'settings_menu' => $this->settingsMenu(), 'settings_menu' => $this->settingsMenu(),
'min_length' => config('min_password_length'), 'min_length' => config('min_password_length'),
] + $this->getNotifications() ]
); );
} }
@ -131,9 +131,9 @@ class SettingsController extends BaseController
]); ]);
if (!empty($user->password) && !$this->auth->verifyPassword($user, $data['password'])) { if (!empty($user->password) && !$this->auth->verifyPassword($user, $data['password'])) {
$this->addNotification('auth.password.error', 'errors'); $this->addNotification('auth.password.error', NotificationType::ERROR);
} elseif ($data['new_password'] != $data['new_password2']) { } elseif ($data['new_password'] != $data['new_password2']) {
$this->addNotification('validation.password.confirmed', 'errors'); $this->addNotification('validation.password.confirmed', NotificationType::ERROR);
} else { } else {
$this->auth->setPassword($user, $data['new_password']); $this->auth->setPassword($user, $data['new_password']);
@ -158,7 +158,7 @@ class SettingsController extends BaseController
'settings_menu' => $this->settingsMenu(), 'settings_menu' => $this->settingsMenu(),
'themes' => $themes, 'themes' => $themes,
'current_theme' => $currentTheme, 'current_theme' => $currentTheme,
] + $this->getNotifications() ]
); );
} }
@ -192,7 +192,7 @@ class SettingsController extends BaseController
'settings_menu' => $this->settingsMenu(), 'settings_menu' => $this->settingsMenu(),
'languages' => $languages, 'languages' => $languages,
'current_language' => $currentLanguage, 'current_language' => $currentLanguage,
] + $this->getNotifications() ]
); );
} }
@ -228,7 +228,7 @@ class SettingsController extends BaseController
[ [
'settings_menu' => $this->settingsMenu(), 'settings_menu' => $this->settingsMenu(),
'providers' => $providers, 'providers' => $providers,
] + $this->getNotifications(), ],
); );
} }

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Engelsystem\Middleware; namespace Engelsystem\Middleware;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Http\Exceptions\HttpException; use Engelsystem\Http\Exceptions\HttpException;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
@ -55,7 +56,10 @@ class ErrorHandler implements MiddlewareInterface
$response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders()); $response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
} catch (ValidationException $e) { } catch (ValidationException $e) {
$response = $this->redirectBack(); $response = $this->redirectBack();
$response->with('errors', ['validation' => $e->getValidator()->getErrors()]); $response->with(
'messages.' . NotificationType::ERROR->value,
['validation' => $e->getValidator()->getErrors()]
);
if ($request instanceof Request) { if ($request instanceof Request) {
$response->withInput(Arr::except($request->request->all(), $this->formIgnore)); $response->withInput(Arr::except($request->request->all(), $this->formIgnore));

View File

@ -26,7 +26,6 @@ class Legacy extends TwigExtension
new TwigFunction('menuUserHints', 'header_render_hints', $isSafeHtml), new TwigFunction('menuUserHints', 'header_render_hints', $isSafeHtml),
new TwigFunction('menuLanguages', 'make_language_select', $isSafeHtml), new TwigFunction('menuLanguages', 'make_language_select', $isSafeHtml),
new TwigFunction('page', [$this, 'getPage']), new TwigFunction('page', [$this, 'getPage']),
new TwigFunction('msg', 'msg', $isSafeHtml),
]; ];
} }

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Renderer\Twig\Extensions;
use Engelsystem\Controllers\HasUserNotifications;
use Engelsystem\Controllers\NotificationType;
use Illuminate\Support\Collection;
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\TwigFunction;
class Notification extends TwigExtension
{
use HasUserNotifications;
public function __construct(protected SymfonySession $session)
{
}
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
return [
new TwigFunction('notifications', [$this, 'notifications']),
];
}
/**
* @return Collection|Collection[]
*/
public function notifications(string $type = null): Collection
{
$types = $type ? [NotificationType::from($type)] : null;
$messages = $this->getNotifications($types);
if ($types) {
$messages = $messages[$type] ?? [];
}
return collect($messages);
}
}

View File

@ -14,6 +14,7 @@ use Engelsystem\Renderer\Twig\Extensions\Develop;
use Engelsystem\Renderer\Twig\Extensions\Globals; use Engelsystem\Renderer\Twig\Extensions\Globals;
use Engelsystem\Renderer\Twig\Extensions\Legacy; use Engelsystem\Renderer\Twig\Extensions\Legacy;
use Engelsystem\Renderer\Twig\Extensions\Markdown; use Engelsystem\Renderer\Twig\Extensions\Markdown;
use Engelsystem\Renderer\Twig\Extensions\Notification;
use Engelsystem\Renderer\Twig\Extensions\Session; use Engelsystem\Renderer\Twig\Extensions\Session;
use Engelsystem\Renderer\Twig\Extensions\Translation; use Engelsystem\Renderer\Twig\Extensions\Translation;
use Engelsystem\Renderer\Twig\Extensions\Url; use Engelsystem\Renderer\Twig\Extensions\Url;
@ -34,6 +35,7 @@ class TwigServiceProvider extends ServiceProvider
'csrf' => Csrf::class, 'csrf' => Csrf::class,
'develop' => Develop::class, 'develop' => Develop::class,
'globals' => Globals::class, 'globals' => Globals::class,
'notification' => Notification::class,
'twigmodel' => TwigModel::class, 'twigmodel' => TwigModel::class,
'session' => Session::class, 'session' => Session::class,
'legacy' => Legacy::class, 'legacy' => Legacy::class,

View File

@ -5,12 +5,11 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Admin; namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Controllers\Admin\FaqController; use Engelsystem\Controllers\Admin\FaqController;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Validation\Validator; use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\Faq; use Engelsystem\Models\Faq;
use Engelsystem\Test\Unit\Controllers\ControllerTest; use Engelsystem\Test\Unit\Controllers\ControllerTest;
use Illuminate\Support\Collection;
use Symfony\Component\HttpFoundation\Session\Session;
class FaqControllerTest extends ControllerTest class FaqControllerTest extends ControllerTest
{ {
@ -33,10 +32,7 @@ class FaqControllerTest extends ControllerTest
->willReturnCallback(function ($view, $data) { ->willReturnCallback(function ($view, $data) {
$this->assertEquals('pages/faq/edit.twig', $view); $this->assertEquals('pages/faq/edit.twig', $view);
/** @var Collection $warnings */
$warnings = $data['messages'];
$this->assertNotEmpty($data['faq']); $this->assertNotEmpty($data['faq']);
$this->assertTrue($warnings->isEmpty());
return $this->response; return $this->response;
}); });
@ -45,6 +41,7 @@ class FaqControllerTest extends ControllerTest
$controller = $this->app->make(FaqController::class); $controller = $this->app->make(FaqController::class);
$controller->edit($this->request); $controller->edit($this->request);
$this->assertHasNoNotifications(NotificationType::WARNING);
} }
/** /**
@ -82,14 +79,10 @@ class FaqControllerTest extends ControllerTest
$this->assertTrue($this->log->hasInfoThatContains('Updated')); $this->assertTrue($this->log->hasInfoThatContains('Updated'));
/** @var Session $session */
$session = $this->app->get('session');
$messages = $session->get('messages');
$this->assertEquals('faq.edit.success', $messages[0]);
$faq = (new Faq())->find(2); $faq = (new Faq())->find(2);
$this->assertEquals('Foo?', $faq->question); $this->assertEquals('Foo?', $faq->question);
$this->assertEquals('Bar!', $faq->text); $this->assertEquals('Bar!', $faq->text);
$this->assertHasNotification('faq.edit.success');
} }
/** /**
@ -153,10 +146,7 @@ class FaqControllerTest extends ControllerTest
$this->assertTrue($this->log->hasInfoThatContains('Deleted')); $this->assertTrue($this->log->hasInfoThatContains('Deleted'));
/** @var Session $session */ $this->assertHasNotification('faq.delete.success');
$session = $this->app->get('session');
$messages = $session->get('messages');
$this->assertEquals('faq.delete.success', $messages[0]);
} }
/** /**

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Admin; namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Controllers\Admin\NewsController; use Engelsystem\Controllers\Admin\NewsController;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Events\EventDispatcher; use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
@ -12,9 +13,7 @@ use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\News; use Engelsystem\Models\News;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\ControllerTest; use Engelsystem\Test\Unit\Controllers\ControllerTest;
use Illuminate\Support\Collection;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\HttpFoundation\Session\Session;
class NewsControllerTest extends ControllerTest class NewsControllerTest extends ControllerTest
{ {
@ -42,10 +41,7 @@ class NewsControllerTest extends ControllerTest
->willReturnCallback(function ($view, $data) { ->willReturnCallback(function ($view, $data) {
$this->assertEquals('pages/news/edit.twig', $view); $this->assertEquals('pages/news/edit.twig', $view);
/** @var Collection $warnings */
$warnings = $data['warnings'];
$this->assertNotEmpty($data['news']); $this->assertNotEmpty($data['news']);
$this->assertTrue($warnings->isEmpty());
return $this->response; return $this->response;
}); });
@ -54,6 +50,7 @@ class NewsControllerTest extends ControllerTest
$controller = $this->app->make(NewsController::class); $controller = $this->app->make(NewsController::class);
$controller->edit($this->request); $controller->edit($this->request);
$this->assertHasNoNotifications(NotificationType::WARNING);
} }
/** /**
@ -147,10 +144,7 @@ class NewsControllerTest extends ControllerTest
$this->assertTrue($this->log->hasInfoThatContains('Updated')); $this->assertTrue($this->log->hasInfoThatContains('Updated'));
/** @var Session $session */ $this->assertHasNotification('news.edit.success');
$session = $this->app->get('session');
$messages = $session->get('messages');
$this->assertEquals('news.edit.success', $messages[0]);
$news = (new News())->find($id); $news = (new News())->find($id);
$this->assertEquals($text, $news->text); $this->assertEquals($text, $news->text);
@ -224,10 +218,7 @@ class NewsControllerTest extends ControllerTest
$this->assertTrue($this->log->hasInfoThatContains('Deleted')); $this->assertTrue($this->log->hasInfoThatContains('Deleted'));
/** @var Session $session */ $this->assertHasNotification('news.delete.success');
$session = $this->app->get('session');
$messages = $session->get('messages');
$this->assertEquals('news.delete.success', $messages[0]);
} }
/** /**

View File

@ -7,6 +7,7 @@ namespace Engelsystem\Test\Unit\Controllers;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Controllers\AuthController; use Engelsystem\Controllers\AuthController;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
@ -16,13 +17,12 @@ use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\User\Settings; use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class AuthControllerTest extends TestCase class AuthControllerTest extends ControllerTest
{ {
use ArraySubsetAsserts; use ArraySubsetAsserts;
use HasDatabase; use HasDatabase;
@ -42,11 +42,6 @@ class AuthControllerTest extends TestCase
/** @var Authenticator|MockObject $auth */ /** @var Authenticator|MockObject $auth */
list(, $session, $redirect, $config, $auth) = $this->getMocks(); list(, $session, $redirect, $config, $auth) = $this->getMocks();
$session->expects($this->atLeastOnce())
->method('get')
->willReturnCallback(function ($type) {
return $type == 'errors' ? ['foo' => 'bar'] : [];
});
$response->expects($this->once()) $response->expects($this->once())
->method('withView') ->method('withView')
->with('pages/login') ->with('pages/login')
@ -70,11 +65,10 @@ class AuthControllerTest extends TestCase
/** @var Config $config */ /** @var Config $config */
/** @var Authenticator|MockObject $auth */ /** @var Authenticator|MockObject $auth */
list(, , $redirect, $config, $auth) = $this->getMocks(); list(, , $redirect, $config, $auth) = $this->getMocks();
$session = new Session(new MockArraySessionStorage()); $this->session = new Session(new MockArraySessionStorage());
$this->app->instance('session', $this->session);
/** @var Validator|MockObject $validator */ /** @var Validator|MockObject $validator */
$validator = new Validator(); $validator = new Validator();
$session->set('errors', [['bar' => 'some.bar.error']]);
$this->app->instance('session', $session);
$user = $this->createUser(); $user = $this->createUser();
$auth->expects($this->exactly(2)) $auth->expects($this->exactly(2))
@ -86,13 +80,12 @@ class AuthControllerTest extends TestCase
->method('withView') ->method('withView')
->willReturnCallback(function ($view, $data = []) use ($response) { ->willReturnCallback(function ($view, $data = []) use ($response) {
$this->assertEquals('pages/login', $view); $this->assertEquals('pages/login', $view);
$this->assertArraySubset(['errors' => collect(['some.bar.error', 'auth.not-found'])], $data);
return $response; return $response;
}); });
/** @var AuthController|MockObject $controller */ /** @var AuthController|MockObject $controller */
$controller = $this->getMockBuilder(AuthController::class) $controller = $this->getMockBuilder(AuthController::class)
->setConstructorArgs([$response, $session, $redirect, $config, $auth]) ->setConstructorArgs([$response, $this->session, $redirect, $config, $auth])
->onlyMethods(['loginUser']) ->onlyMethods(['loginUser'])
->getMock(); ->getMock();
$controller->setValidator($validator); $controller->setValidator($validator);
@ -120,7 +113,7 @@ class AuthControllerTest extends TestCase
// No user found // No user found
$request = new Request([], ['login' => 'foo', 'password' => 'bar']); $request = new Request([], ['login' => 'foo', 'password' => 'bar']);
$controller->postLogin($request); $controller->postLogin($request);
$this->assertEquals([], $session->all()); $this->assertHasNotification('auth.not-found', NotificationType::ERROR);
// Authenticated user // Authenticated user
$controller->postLogin($request); $controller->postLogin($request);

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers; namespace Engelsystem\Test\Unit\Controllers;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator; use Engelsystem\Http\UrlGenerator;
@ -33,12 +34,26 @@ abstract class ControllerTest extends TestCase
protected Session $session; protected Session $session;
/** /**
* @param string|null $type * @param string|string[] $value
*/ */
protected function assertHasNotification(string $value, string $type = 'messages'): void protected function setNotification(string|array $value, NotificationType $type = NotificationType::MESSAGE): void
{ {
$messages = $this->session->get($type, []); $this->session->set(
$this->assertTrue(in_array($value, $messages)); 'messages.' . $type->value,
array_merge($this->session->get('messages.' . $type->value, []), (array) $value)
);
}
protected function assertHasNotification(string $value, NotificationType $type = NotificationType::MESSAGE): void
{
$messages = $this->session->get('messages.' . $type->value, []);
$this->assertTrue(in_array($value, $messages), 'Has ' . $type->value . ' notification: ' . $value);
}
protected function assertHasNoNotifications(NotificationType $type = null): void
{
$messages = $this->session->get('messages' . ($type ? '.' . $type->value : ''), []);
$this->assertEmpty($messages, 'Has no' . ($type ? ' ' . $type->value : '') . ' notification.');
} }
/** /**

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers; namespace Engelsystem\Test\Unit\Controllers;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Test\Unit\Controllers\Stub\HasUserNotificationsImplementation; use Engelsystem\Test\Unit\Controllers\Stub\HasUserNotificationsImplementation;
use Engelsystem\Test\Unit\TestCase; use Engelsystem\Test\Unit\TestCase;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -22,16 +23,17 @@ class HasUserNotificationsTest extends TestCase
$this->app->instance('session', $session); $this->app->instance('session', $session);
$notify = new HasUserNotificationsImplementation(); $notify = new HasUserNotificationsImplementation();
$notify->add('Foo', 'errors'); $notify->add('Foo', NotificationType::ERROR);
$notify->add('Bar', 'warnings'); $notify->add('Bar', NotificationType::WARNING);
$notify->add(['Baz', 'Lorem'], 'information'); $notify->add(['Baz', 'Lorem'], NotificationType::INFORMATION);
$notify->add(['Hm', ['Uff', 'sum']], 'messages'); $notify->add(['Hm', ['test'], 'some' => ['Uff', 'sum']], NotificationType::MESSAGE);
$notify->add(['some' => ['it']], NotificationType::MESSAGE);
$this->assertEquals([ $this->assertEquals([
'errors' => new Collection(['Foo']), NotificationType::ERROR->value => new Collection(['Foo']),
'warnings' => new Collection(['Bar']), NotificationType::WARNING->value => new Collection(['Bar']),
'information' => new Collection(['Baz', 'Lorem']), NotificationType::INFORMATION->value => new Collection(['Baz', 'Lorem']),
'messages' => new Collection(['Hm', 'Uff', 'sum']), NotificationType::MESSAGE->value => new Collection(['Hm', 'test', 'Uff', 'sum', 'it']),
], $notify->get()); ], $notify->get());
} }
} }

View File

@ -6,6 +6,7 @@ namespace Engelsystem\Test\Unit\Controllers;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Controllers\PasswordResetController; use Engelsystem\Controllers\PasswordResetController;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpNotFound; use Engelsystem\Http\Exceptions\HttpNotFound;
@ -18,13 +19,12 @@ use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Renderer\Renderer; use Engelsystem\Renderer\Renderer;
use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\Test\TestLogger; use Psr\Log\Test\TestLogger;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class PasswordResetControllerTest extends TestCase class PasswordResetControllerTest extends ControllerTest
{ {
use ArraySubsetAsserts; use ArraySubsetAsserts;
use HasDatabase; use HasDatabase;
@ -55,7 +55,7 @@ class PasswordResetControllerTest extends TestCase
$controller = $this->getController( $controller = $this->getController(
'pages/password/reset-success', 'pages/password/reset-success',
['type' => 'email', 'errors' => collect()] ['type' => 'email']
); );
/** @var TestLogger $log */ /** @var TestLogger $log */
$log = $this->args['log']; $log = $this->args['log'];
@ -67,6 +67,7 @@ class PasswordResetControllerTest extends TestCase
$this->assertNotEmpty((new PasswordReset())->find($user->id)->first()); $this->assertNotEmpty((new PasswordReset())->find($user->id)->first());
$this->assertTrue($log->hasInfoThatContains($user->name)); $this->assertTrue($log->hasInfoThatContains($user->name));
$this->assertHasNoNotifications();
} }
/** /**
@ -92,10 +93,11 @@ class PasswordResetControllerTest extends TestCase
$controller = $this->getController( $controller = $this->getController(
'pages/password/reset-success', 'pages/password/reset-success',
['type' => 'email', 'errors' => collect()] ['type' => 'email']
); );
$controller->postReset($request); $controller->postReset($request);
$this->assertHasNoNotifications();
} }
/** /**
@ -148,7 +150,7 @@ class PasswordResetControllerTest extends TestCase
$controller = $this->getController( $controller = $this->getController(
'pages/password/reset-success', 'pages/password/reset-success',
['type' => 'reset', 'errors' => collect()] ['type' => 'reset']
); );
$auth = new Authenticator($request, $this->args['session'], $user); $auth = new Authenticator($request, $this->args['session'], $user);
@ -159,6 +161,7 @@ class PasswordResetControllerTest extends TestCase
$this->assertEmpty((new PasswordReset())->find($user->id)); $this->assertEmpty((new PasswordReset())->find($user->id));
$this->assertNotNull(auth()->authenticate($user->name, $password)); $this->assertNotNull(auth()->authenticate($user->name, $password));
$this->assertHasNoNotifications();
} }
/** /**
@ -179,16 +182,10 @@ class PasswordResetControllerTest extends TestCase
['token' => $token->token] ['token' => $token->token]
); );
$controller = $this->getController( $controller = $this->getController('pages/password/reset-form');
'pages/password/reset-form',
['errors' => collect(['some.other.error', 'validation.password.confirmed'])]
);
/** @var Session $session */
$session = $this->args['session'];
$session->set('errors', ['foo' => ['bar' => 'some.other.error']]);
$controller->postResetPassword($request); $controller->postResetPassword($request);
$this->assertEmpty($session->get('errors')); $this->assertHasNotification('validation.password.confirmed', NotificationType::ERROR);
} }
protected function getControllerArgs(): array protected function getControllerArgs(): array
@ -203,6 +200,10 @@ class PasswordResetControllerTest extends TestCase
$this->app->instance('session', $session); $this->app->instance('session', $session);
$this->session = $session;
$this->response = $response;
$this->log = $log;
return $this->args = [ return $this->args = [
'response' => $response, 'response' => $response,
'session' => $session, 'session' => $session,

View File

@ -6,6 +6,7 @@ namespace Engelsystem\Test\Unit\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Controllers\SettingsController; use Engelsystem\Controllers\SettingsController;
use Engelsystem\Http\Exceptions\HttpNotFound; use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
@ -133,7 +134,7 @@ class SettingsControllerTest extends ControllerTest
$this->setUpProfileTest(); $this->setUpProfileTest();
config(['buildup_start' => new Carbon('2022-01-02')]); // arrival before buildup config(['buildup_start' => new Carbon('2022-01-02')]); // arrival before buildup
$this->controller->saveProfile($this->request); $this->controller->saveProfile($this->request);
$this->assertHasNotification('settings.profile.planned_arrival_date.invalid', 'errors'); $this->assertHasNotification('settings.profile.planned_arrival_date.invalid', NotificationType::ERROR);
} }
/** /**
@ -144,7 +145,7 @@ class SettingsControllerTest extends ControllerTest
$this->setUpProfileTest(); $this->setUpProfileTest();
config(['teardown_end' => new Carbon('2022-01-01')]); // departure after teardown config(['teardown_end' => new Carbon('2022-01-01')]); // departure after teardown
$this->controller->saveProfile($this->request); $this->controller->saveProfile($this->request);
$this->assertHasNotification('settings.profile.planned_departure_date.invalid', 'errors'); $this->assertHasNotification('settings.profile.planned_departure_date.invalid', NotificationType::ERROR);
} }
/** /**
@ -272,7 +273,7 @@ class SettingsControllerTest extends ControllerTest
/** @var Session $session */ /** @var Session $session */
$session = $this->app->get('session'); $session = $this->app->get('session');
$messages = $session->get('messages'); $messages = $session->get('messages.' . NotificationType::MESSAGE->value);
$this->assertEquals('settings.password.success', $messages[0]); $this->assertEquals('settings.password.success', $messages[0]);
} }
@ -328,10 +329,7 @@ class SettingsControllerTest extends ControllerTest
$this->controller->savePassword($this->request); $this->controller->savePassword($this->request);
/** @var Session $session */ $this->assertHasNotification('auth.password.error', NotificationType::ERROR);
$session = $this->app->get('session');
$errors = $session->get('errors');
$this->assertEquals('auth.password.error', $errors[0]);
} }
/** /**
@ -359,10 +357,7 @@ class SettingsControllerTest extends ControllerTest
$this->controller->savePassword($this->request); $this->controller->savePassword($this->request);
/** @var Session $session */ $this->assertHasNotification('validation.password.confirmed', NotificationType::ERROR);
$session = $this->app->get('session');
$errors = $session->get('errors');
$this->assertEquals('validation.password.confirmed', $errors[0]);
} }
public function savePasswordValidationProvider(): array public function savePasswordValidationProvider(): array
@ -558,7 +553,6 @@ class SettingsControllerTest extends ControllerTest
->method('withView') ->method('withView')
->willReturnCallback(function ($view, $data) use ($providers) { ->willReturnCallback(function ($view, $data) use ($providers) {
$this->assertEquals('pages/settings/oauth', $view); $this->assertEquals('pages/settings/oauth', $view);
$this->assertArrayHasKey('information', $data);
$this->assertArrayHasKey('providers', $data); $this->assertArrayHasKey('providers', $data);
$this->assertEquals($providers, $data['providers']); $this->assertEquals($providers, $data['providers']);

View File

@ -5,12 +5,13 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Stub; namespace Engelsystem\Test\Unit\Controllers\Stub;
use Engelsystem\Controllers\HasUserNotifications; use Engelsystem\Controllers\HasUserNotifications;
use Engelsystem\Controllers\NotificationType;
class HasUserNotificationsImplementation class HasUserNotificationsImplementation
{ {
use HasUserNotifications; use HasUserNotifications;
public function add(string|array $value, string $type = 'messages'): void public function add(string|array $value, NotificationType $type = NotificationType::MESSAGE): void
{ {
$this->addNotification($value, $type); $this->addNotification($value, $type);
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Middleware; namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Http\Exceptions\HttpException; use Engelsystem\Http\Exceptions\HttpException;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Psr7ServiceProvider; use Engelsystem\Http\Psr7ServiceProvider;
@ -185,7 +186,10 @@ class ErrorHandlerTest extends TestCase
->willReturn(['foo' => ['validation.foo.numeric']]); ->willReturn(['foo' => ['validation.foo.numeric']]);
$session = new Session(new MockArraySessionStorage()); $session = new Session(new MockArraySessionStorage());
$session->set('errors', ['validation' => ['foo' => ['validation.foo.required']]]); $session->set(
'messages.' . NotificationType::ERROR->value,
['validation' => ['foo' => ['validation.foo.required']]]
);
$request = Request::create( $request = Request::create(
'/foo/bar', '/foo/bar',
'POST', 'POST',
@ -208,7 +212,7 @@ class ErrorHandlerTest extends TestCase
$this->assertEquals(302, $return->getStatusCode()); $this->assertEquals(302, $return->getStatusCode());
$this->assertEquals('http://localhost/', $return->getHeaderLine('location')); $this->assertEquals('http://localhost/', $return->getHeaderLine('location'));
$this->assertEquals([ $this->assertEquals([
'errors' => [ 'messages.' . NotificationType::ERROR->value => [
'validation' => [ 'validation' => [
'foo' => [ 'foo' => [
'validation.foo.required', 'validation.foo.required',

View File

@ -27,7 +27,6 @@ class LegacyTest extends ExtensionTest
$this->assertExtensionExists('menuUserHints', 'header_render_hints', $functions, $isSafeHtml); $this->assertExtensionExists('menuUserHints', 'header_render_hints', $functions, $isSafeHtml);
$this->assertExtensionExists('menuLanguages', 'make_language_select', $functions, $isSafeHtml); $this->assertExtensionExists('menuLanguages', 'make_language_select', $functions, $isSafeHtml);
$this->assertExtensionExists('page', [$extension, 'getPage'], $functions); $this->assertExtensionExists('page', [$extension, 'getPage'], $functions);
$this->assertExtensionExists('msg', 'msg', $functions, $isSafeHtml);
} }
/** /**

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
use Engelsystem\Controllers\NotificationType;
use Engelsystem\Renderer\Twig\Extensions\Notification;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class NotificationTest extends ExtensionTest
{
/**
* @covers \Engelsystem\Renderer\Twig\Extensions\Notification::__construct
* @covers \Engelsystem\Renderer\Twig\Extensions\Notification::getFunctions
*/
public function testGetFunctions(): void
{
$session = new Session(new MockArraySessionStorage());
$extension = new Notification($session);
$functions = $extension->getFunctions();
$this->assertExtensionExists('notifications', [$extension, 'notifications'], $functions);
}
/**
* @covers \Engelsystem\Renderer\Twig\Extensions\Notification::notifications
*/
public function testNotifications(): void
{
$session = new Session(new MockArraySessionStorage());
$extension = new Notification($session);
$this->app->instance('session', $session);
$notificationsList = $extension->notifications()->toArray();
$this->assertIsArray($notificationsList);
foreach ($notificationsList as $notification) {
$this->assertEmpty($notification);
}
$session->set('messages.' . NotificationType::ERROR->value, 'some error');
$session->set('messages.' . NotificationType::WARNING->value, 'a warning');
$session->set('messages.' . NotificationType::INFORMATION->value, 'for your information');
$session->set('messages.' . NotificationType::MESSAGE->value, 'i\'m a message');
$notifications = $extension->notifications();
$this->assertEquals(['some error'], $notifications[NotificationType::ERROR->value]->toArray());
$this->assertEquals(['a warning'], $notifications[NotificationType::WARNING->value]->toArray());
$this->assertEquals(['for your information'], $notifications[NotificationType::INFORMATION->value]->toArray());
$this->assertEquals(['i\'m a message'], $notifications[NotificationType::MESSAGE->value]->toArray());
$session->set('messages.' . NotificationType::ERROR->value, 'Test error');
$notifications = $extension->notifications(NotificationType::ERROR->value);
$this->assertEquals(['Test error'], $notifications->toArray());
}
}