ifsg: can be confirmed and edited by admins

This commit is contained in:
Xu 2024-03-05 17:42:38 +01:00 committed by Igor Scheller
parent 400edd9a19
commit 873803eb2d
19 changed files with 666 additions and 48 deletions

View File

@ -51,6 +51,15 @@ $route->addGroup(
} }
); );
// User admin settings
$route->addGroup(
'/users/{user_id:\d+}',
function (RouteCollector $route): void {
$route->get('/certificates', 'Admin\\UserSettingsController@certificate');
$route->post('/certificates/ifsg', 'Admin\\UserSettingsController@saveIfsgCertificate');
}
);
// Password recovery // Password recovery
$route->addGroup( $route->addGroup(
'/password/reset', '/password/reset',

View File

@ -24,6 +24,7 @@ class LicenseFactory extends Factory
$ifsg_certificate = $this->faker->boolean(0.1); $ifsg_certificate = $this->faker->boolean(0.1);
$ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate; $ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate;
$ifsg_confirmed = $this->faker->boolean(0.5) && ($ifsg_certificate || $ifsg_certificate_light);
return [ return [
'user_id' => User::factory(), 'user_id' => User::factory(),
@ -35,6 +36,7 @@ class LicenseFactory extends Factory
'drive_12t' => $drive_12t, 'drive_12t' => $drive_12t,
'ifsg_certificate' => $ifsg_certificate, 'ifsg_certificate' => $ifsg_certificate,
'ifsg_certificate_light' => $ifsg_certificate_light, 'ifsg_certificate_light' => $ifsg_certificate_light,
'ifsg_confirmed' => $ifsg_confirmed,
]; ];
} }
} }

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddIfsgConfirmedToUsersLicenses extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('users_licenses', function (Blueprint $table): void {
$table->boolean('ifsg_confirmed')->default(false)->after('ifsg_certificate');
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('users_licenses', function (Blueprint $table): void {
$table->dropColumn('ifsg_confirmed');
});
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class AddUserIfsgEditPermission extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->insert([
'name' => 'user.ifsg.edit', 'description' => 'Edit IfSG Certificate',
]);
$editIfsg = $db->table('privileges')
->where('name', 'user.ifsg.edit')
->get(['id'])
->first();
$shico = 60;
$team_coordinator = 65;
$db->table('group_privileges')
->insertOrIgnore([
['group_id' => $shico, 'privilege_id' => $editIfsg->id],
['group_id' => $team_coordinator, 'privilege_id' => $editIfsg->id],
]);
}
/**
* Reverse the migration
*/
public function down(): void
{
$db = $this->schema->getConnection();
$db->table('privileges')
->where('name', 'user.ifsg.edit')
->delete();
}
}

View File

@ -1,6 +1,7 @@
<?php <?php
use Engelsystem\Database\Db; use Engelsystem\Database\Db;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\State; use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
@ -248,6 +249,12 @@ function user_controller()
->with(['user', 'creator']) ->with(['user', 'creator'])
->get(); ->get();
$is_ifsg_supporter = (bool) AngelType::whereRequiresIfsgCertificate(true)
->leftJoin('user_angel_type', 'user_angel_type.angel_type_id', 'angel_types.id')
->where('user_angel_type.user_id', $user->id)
->where('user_angel_type.supporter', true)
->count();
return [ return [
htmlspecialchars($user_source->displayName), htmlspecialchars($user_source->displayName),
User_view( User_view(
@ -261,7 +268,8 @@ function user_controller()
$tshirt_score, $tshirt_score,
auth()->can('admin_active'), auth()->can('admin_active'),
auth()->can('admin_user_worklog'), auth()->can('admin_user_worklog'),
$worklogs $worklogs,
auth()->can('user.ifsg.edit') || $is_ifsg_supporter,
), ),
]; ];
} }

View File

@ -197,10 +197,10 @@ function AngelType_view_buttons(
if ($angeltype->requires_driver_license) { if ($angeltype->requires_driver_license) {
$buttons[] = button( $buttons[] = button(
url('/settings/certificates'), url('/settings/certificates'),
icon('person-vcard') . __('my driving license') icon('person-vcard') . __('My driving license')
); );
} }
if (config('isfg_enabled') && $angeltype->requires_ifsg_certificate) { if (config('ifsg_enabled') && $angeltype->requires_ifsg_certificate) {
$buttons[] = button( $buttons[] = button(
url('/settings/certificates'), url('/settings/certificates'),
icon('card-checklist') . __('angeltype.ifsg.own') icon('card-checklist') . __('angeltype.ifsg.own')
@ -294,14 +294,35 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
$member['has_license_forklift'] = icon_bool($member->license->drive_forklift); $member['has_license_forklift'] = icon_bool($member->license->drive_forklift);
} }
if ($angeltype->requires_ifsg_certificate && config('ifsg_enabled')) { if ($angeltype->requires_ifsg_certificate && config('ifsg_enabled')) {
$member['ifsg_certificate'] = icon_bool($member->license->ifsg_certificate); $ifsg_certificate = $member->license->ifsg_certificate;
$member['ifsg_certificate'] = ($member->license->ifsg_confirmed && $ifsg_certificate)
? icon('check2-all', 'text-success')
: icon_bool($ifsg_certificate);
if (config('ifsg_light_enabled')) { if (config('ifsg_light_enabled')) {
$member['ifsg_certificate_light'] = icon_bool($member->license->ifsg_certificate_light); $ifsg_certificate_light = $member->license->ifsg_certificate_light;
$member['ifsg_certificate_light'] = ($member->license->ifsg_confirmed && $ifsg_certificate_light)
? icon('check2-all', 'text-success')
: icon_bool($ifsg_certificate_light);
} }
} }
$edit_certificates = '';
if (
($angeltype->requires_driver_license || $angeltype->requires_ifsg_certificate)
&& ($admin_user_angeltypes || auth()->can('user.ifsg.edit'))
) {
$edit_certificates =
button(
url('/users/' . $member->id . '/certificates'),
icon('card-checklist'),
'btn-sm',
'',
__('Edit certificates'),
);
}
if ($angeltype->restricted && empty($member->pivot->confirm_user_id)) { if ($angeltype->restricted && empty($member->pivot->confirm_user_id)) {
$member['actions'] = table_buttons([ $member['actions'] = table_buttons([
$edit_certificates,
button( button(
url( url(
'/user-angeltypes', '/user-angeltypes',
@ -323,6 +344,7 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
} elseif ($member->pivot->supporter) { } elseif ($member->pivot->supporter) {
if ($admin_angeltypes || ($admin_user_angeltypes && config('supporters_can_promote'))) { if ($admin_angeltypes || ($admin_user_angeltypes && config('supporters_can_promote'))) {
$member['actions'] = table_buttons([ $member['actions'] = table_buttons([
$edit_certificates,
button( button(
url('/user-angeltypes', [ url('/user-angeltypes', [
'action' => 'update', 'action' => 'update',
@ -336,12 +358,15 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
), ),
]); ]);
} else { } else {
$member['actions'] = ''; $member['actions'] = $edit_certificates
? table_buttons([$edit_certificates,])
: '';
} }
$supporters[] = $member; $supporters[] = $member;
} else { } else {
if ($admin_user_angeltypes) { if ($admin_user_angeltypes) {
$member['actions'] = table_buttons([ $member['actions'] = table_buttons([
$edit_certificates,
($admin_angeltypes || config('supporters_can_promote')) ? ($admin_angeltypes || config('supporters_can_promote')) ?
button( button(
url('/user-angeltypes', [ url('/user-angeltypes', [
@ -366,6 +391,10 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange
__('Remove'), __('Remove'),
), ),
]); ]);
} elseif ($edit_certificates) {
$member['actions'] = table_buttons([
$edit_certificates,
]);
} }
$members_confirmed[] = $member; $members_confirmed[] = $member;
} }
@ -408,7 +437,10 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a
]); ]);
} }
if (config('ifsg_enabled') && $angeltype->requires_ifsg_certificate && ($supporter || $admin_angeltypes)) { if (
config('ifsg_enabled') && $angeltype->requires_ifsg_certificate
&& ($supporter || $admin_angeltypes || auth()->can('user.ifsg.edit'))
) {
if (config('ifsg_light_enabled')) { if (config('ifsg_light_enabled')) {
$headers['ifsg_certificate_light'] = __('ifsg.certificate_light'); $headers['ifsg_certificate_light'] = __('ifsg.certificate_light');
} }

View File

@ -151,7 +151,9 @@ function UserAngelType_add_view(AngelType $angeltype, $users_source, $user_id)
msg(), msg(),
form([ form([
form_info(__('Angeltype'), htmlspecialchars($angeltype->name)), form_info(__('Angeltype'), htmlspecialchars($angeltype->name)),
form_checkbox('auto_confirm_user', __('Confirm user'), true), $angeltype->restricted
? form_checkbox('auto_confirm_user', __('Confirm user'), true)
: '',
form_select('user_id', __('general.user'), $users, $user_id), form_select('user_id', __('general.user'), $users, $user_id),
form_submit('submit', icon('plus-lg') . __('Add')), form_submit('submit', icon('plus-lg') . __('Add')),
]), ]),

View File

@ -532,6 +532,7 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege)
* @param bool $tshirt_admin * @param bool $tshirt_admin
* @param bool $admin_user_worklog_privilege * @param bool $admin_user_worklog_privilege
* @param Worklog[]|Collection $user_worklogs * @param Worklog[]|Collection $user_worklogs
* @param bool $admin_ifsg
* *
* @return string * @return string
*/ */
@ -546,7 +547,8 @@ function User_view(
$tshirt_score, $tshirt_score,
$tshirt_admin, $tshirt_admin,
$admin_user_worklog_privilege, $admin_user_worklog_privilege,
$user_worklogs $user_worklogs,
$admin_ifsg
) { ) {
$goodie = GoodieType::from(config('goodie_type')); $goodie = GoodieType::from(config('goodie_type'));
$goodie_enabled = $goodie !== GoodieType::None; $goodie_enabled = $goodie !== GoodieType::None;
@ -629,6 +631,10 @@ function User_view(
icon('valentine') . __('Vouchers') icon('valentine') . __('Vouchers')
) )
: '', : '',
$admin_ifsg ? button(
url('/users/' . $user_source->id . '/certificates'),
icon('card-checklist') . __('settings.certificates')
) : '',
$admin_user_worklog_privilege ? button( $admin_user_worklog_privilege ? button(
url('/admin/user/' . $user_source->id . '/worklog'), url('/admin/user/' . $user_source->id . '/worklog'),
icon('clock-history') . __('worklog.add') icon('clock-history') . __('worklog.add')

View File

@ -368,6 +368,9 @@ msgstr "Du darfst diesen Benutzer nicht von diesem Engeltyp entfernen."
msgid "User %s removed from %s." msgid "User %s removed from %s."
msgstr "Benutzer %s von %s entfernt." msgstr "Benutzer %s von %s entfernt."
msgid "Edit certificates"
msgstr "Zertifikate bearbeiten"
msgid "Remove angeltype" msgid "Remove angeltype"
msgstr "Engeltyp löschen" msgstr "Engeltyp löschen"
@ -911,7 +914,7 @@ msgstr "Kontakt"
msgid "Primary contact person/desk for user questions." msgid "Primary contact person/desk for user questions."
msgstr "Ansprechpartner für Fragen." msgstr "Ansprechpartner für Fragen."
msgid "my driving license" msgid "My driving license"
msgstr "Meine Führerschein-Infos" msgstr "Meine Führerschein-Infos"
msgid "" msgid ""
@ -1736,11 +1739,28 @@ msgstr "Gabelstapler"
msgid "settings.certificates.ifsg_light" msgid "settings.certificates.ifsg_light"
msgstr "Ich wurde vor Ort nach IfSG §43 (Frikadellendiplom light) belehrt." msgstr "Ich wurde vor Ort nach IfSG §43 (Frikadellendiplom light) belehrt."
msgid "settings.certificates.ifsg_light_admin"
msgstr "Wurde vor Ort nach IfSG §43 (Frikadellendiplom light) belehrt."
msgid "settings.certificates.ifsg" msgid "settings.certificates.ifsg"
msgstr "Ich habe eine Belehrung nach §43 IfSG (Frikadellendiplom) bei meinem Gesundheitsamt " msgstr "Ich habe eine Belehrung nach §43 IfSG (Frikadellendiplom) bei meinem Gesundheitsamt "
"erhalten und innerhalb von 3 Monaten die Zweitbelehrung durch uns oder meinen Arbeitgeber/Koch/Verein bekommen. " "erhalten und innerhalb von 3 Monaten die Zweitbelehrung durch uns oder meinen Arbeitgeber/Koch/Verein bekommen. "
"Zusätzlich ist die Zweitbelehrung nicht älter als zwei Jahre." "Zusätzlich ist die Zweitbelehrung nicht älter als zwei Jahre."
msgid "settings.certificates.ifsg_admin"
msgstr "Hat eine Belehrung nach §43 IfSG (Frikadellendiplom) vom Gesundheitsamt "
"erhalten und innerhalb von 3 Monaten die Zweitbelehrung durch uns oder einen Arbeitgeber/Koch/Verein bekommen. "
"Zusätzlich ist die Zweitbelehrung nicht älter als zwei Jahre."
msgid "settings.certificates.confirmed"
msgstr "Zertifikat bestätigt"
msgid "settings.certificates.ifsg_confirmed.hint"
msgstr "Deine Gesundheitsbelehrung wurde bestätigt, du kannst deine Angaben nicht mehr selber ändern."
msgid "settings.certificates.confirmation.info"
msgstr "Du hast persönlich überprüft, dass die Zertifizierung / Bescheinigung den Anforderungen genügt."
msgid "settings.certificates.success" msgid "settings.certificates.success"
msgstr "Zertifikate wurden erfolgreich aktualisiert." msgstr "Zertifikate wurden erfolgreich aktualisiert."

View File

@ -437,11 +437,28 @@ msgstr "Forklift"
msgid "settings.certificates.ifsg_light" msgid "settings.certificates.ifsg_light"
msgstr "I was instructed about IfSG §43 (aka Frikadellendiplom light) on site." msgstr "I was instructed about IfSG §43 (aka Frikadellendiplom light) on site."
msgid "settings.certificates.ifsg_light_admin"
msgstr "Was instructed about IfSG §43 (aka Frikadellendiplom light) on site."
msgid "settings.certificates.ifsg" msgid "settings.certificates.ifsg"
msgstr "I have gotten the instruction about §43 IfSG (aka Frikadellendiplom) from my Health Department " msgstr "I have gotten the instruction about §43 IfSG (aka Frikadellendiplom) from my Health Department "
"and a second instruction from us or my employer/chef/assosiation within 3 months. " "and a second instruction from us or my employer/chef/association within 3 months. "
"Additionally my second instruction is not older than 2 years." "Additionally my second instruction is not older than 2 years."
msgid "settings.certificates.ifsg_admin"
msgstr "Got the instruction about §43 IfSG (aka Frikadellendiplom) from a Health Department "
"and a second instruction from us or his employer/chef/association within 3 months. "
"Additionally the second instruction is not older than 2 years."
msgid "settings.certificates.confirmed"
msgstr "Certificate confirmed"
msgid "settings.certificates.ifsg_confirmed.hint"
msgstr "Your health instruction has been confirmed, you can no longer change it by yourself."
msgid "settings.certificates.confirmation.info"
msgstr "You personally checked that the certificate / license meets the requirements."
msgid "settings.certificates.success" msgid "settings.certificates.success"
msgstr "Certificates were updated successfully." msgstr "Certificates were updated successfully."
@ -449,13 +466,13 @@ msgid "angeltype.ifsg.required"
msgstr "Requires health instruction" msgstr "Requires health instruction"
msgid "ifsg.certificate" msgid "ifsg.certificate"
msgstr "health instruction" msgstr "Health instruction"
msgid "ifsg.certificate_light" msgid "ifsg.certificate_light"
msgstr "health instruction on site" msgstr "Health instruction on site"
msgid "angeltype.ifsg.own" msgid "angeltype.ifsg.own"
msgstr "my health instruction" msgstr "My health instruction"
msgid "angeltype.ifsg.required.info" msgid "angeltype.ifsg.required.info"
msgstr "This angeltype requires a health instruction. Please enter your health instruction information!" msgstr "This angeltype requires a health instruction. Please enter your health instruction information!"

View File

@ -0,0 +1,43 @@
{% extends 'pages/settings/certificates.twig' %}
{% import 'macros/form.twig' as f %}
{% import 'macros/base.twig' as m %}
{% block row_content %}
<div class="row">
{% if config('ifsg_enabled') %}
<form
action="{{ url('/users/' ~ admin_user.id ~ '/certificates/ifsg') }}"
enctype="multipart/form-data" method="post"
>
{{ csrf() }}
<div class="col-md-12 pb-3">
<h3>{{ __('settings.certificates.title.ifsg') }}</h3>
{% if config('ifsg_light_enabled') %}
{{ f.checkbox('ifsg_certificate_light', __('settings.certificates.ifsg_light_admin'), {
'checked': certificates.ifsg_certificate_light,
}) }}
{% endif %}
{{ f.checkbox('ifsg_certificate', __('settings.certificates.ifsg_admin'), {
'checked': certificates.ifsg_certificate,
}) }}
<div class="row">
<div class="col-auto">
{{ f.submit(__('form.save'), {'icon_left': 'save'}) }}
</div>
<div class="col-auto">
{{ f.checkbox(
'ifsg_confirmed',
__('settings.certificates.confirmed') ~ f.info(__('settings.certificates.confirmation.info')),
{'raw_label': true, 'checked': certificates.ifsg_confirmed}
) }}
</div>
</div>
</div>
</form>
{% endif %}
</div>
{% endblock %}

View File

@ -7,20 +7,33 @@
{% block row_content %} {% block row_content %}
<div class="row"> <div class="row">
{% if config('ifsg_enabled') %} {% if config('ifsg_enabled') %}
<form action="{{ url('/settings/certificates/ifsg') }}" enctype="multipart/form-data" method="post"> <form
action="{{ url('/settings/certificates/ifsg') }}"
enctype="multipart/form-data" method="post"
>
{{ csrf() }} {{ csrf() }}
<div class="col-md-12 pb-3"> <div class="col-md-12 pb-3">
<h3>{{ __('settings.certificates.title.ifsg') }}</h3> <h3>{{ __('settings.certificates.title.ifsg') }}</h3>
{{ m.info(__('settings.certificates.info')) }} {{ m.info(__('settings.certificates.info')) }}
{% if certificates.ifsg_confirmed %}
<p class="text-success">{{ __('settings.certificates.ifsg_confirmed.hint') }}</p>
{% endif %}
{% if config('ifsg_light_enabled') %} {% if config('ifsg_light_enabled') %}
{{ f.checkbox('ifsg_certificate_light', __('settings.certificates.ifsg_light'), { {{ f.checkbox('ifsg_certificate_light', __('settings.certificates.ifsg_light'), {
'checked': certificates.ifsg_certificate_light, 'checked': certificates.ifsg_certificate_light,
'disabled': certificates.ifsg_confirmed,
}) }} }) }}
{% endif %} {% endif %}
{{ f.checkbox('ifsg_certificate', __('settings.certificates.ifsg'), { {{ f.checkbox('ifsg_certificate', __('settings.certificates.ifsg'), {
'checked': certificates.ifsg_certificate, 'checked': certificates.ifsg_certificate,
'disabled': certificates.ifsg_confirmed,
}) }} }) }}
{% if not certificates.ifsg_confirmed %}
{{ f.submit(__('form.save'), {'icon_left': 'save'}) }} {{ f.submit(__('form.save'), {'icon_left': 'save'}) }}
{% endif %}
</div> </div>
</form> </form>
{% endif %} {% endif %}

View File

@ -8,7 +8,12 @@
{% block container_title %} {% block container_title %}
<h1 id="settings-title"> <h1 id="settings-title">
{{ __('settings.settings') }} {{ __('settings.settings') }}
<small class="text-muted">{{ block('title') }}</small> <small class="text-muted">
{{ block('title') }}
{% if is_admin|default(false) %}
({{ admin_user.name }})
{% endif %}
</small>
</h1> </h1>
{% endblock %} {% endblock %}
@ -16,12 +21,14 @@
<div class="col-md-3 settings-menu"> <div class="col-md-3 settings-menu">
<ul class="nav nav-pills flex-column mt-3 user-settings"> <ul class="nav nav-pills flex-column mt-3 user-settings">
{% for url,title in settings_menu %} {% for url,title in settings_menu %}
<li class="nav-item{% if title.hidden ?? false and url != request.url() %} d-none{% endif %}"> {% if not title.permission|default(false) or has_permission_to(title.permission) %}
<li class="nav-item{% if title.hidden|default(false) and url != request.url() %} d-none{% endif %}">
<a class="nav-link {% if url == request.url() %}active{% endif %}" href="{{ url }}"> <a class="nav-link {% if url == request.url() %}active{% endif %}" href="{{ url }}">
{{ m.icon(title.icon ?? 'gear-fill') }} {{ m.icon(title.icon ?? 'gear-fill') }}
{{ __(title.title ?? title) }} {{ __(title.title ?? title) }}
</a> </a>
</li> </li>
{% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Controllers\Admin;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\BaseController;
use Engelsystem\Controllers\HasUserNotifications;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Response;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User;
use Psr\Log\LoggerInterface;
class UserSettingsController extends BaseController
{
use HasUserNotifications;
public function __construct(
protected Authenticator $auth,
protected Config $config,
protected LoggerInterface $log,
protected Redirector $redirect,
protected Response $response
) {
}
public function certificate(Request $request): Response
{
if (!config('ifsg_enabled')) {
throw new HttpNotFound();
}
$this->checkPermission('user.ifsg.edit', $this->isIfsgSupporter());
$user = $this->getUser($request);
return $this->view(
$user,
'pages/settings/certificates-admin',
[
'certificates' => $user->license,
]
);
}
public function saveIfsgCertificate(Request $request): Response
{
if (!config('ifsg_enabled')) {
throw new HttpNotFound();
}
$this->checkPermission('user.ifsg.edit', $this->isIfsgSupporter());
$user = $this->getUser($request);
$data = $this->validate($request, [
'ifsg_certificate_light' => 'optional|checked',
'ifsg_certificate' => 'optional|checked',
'ifsg_confirmed' => 'optional|checked',
]);
if (config('ifsg_light_enabled')) {
$user->license->ifsg_certificate_light = !$data['ifsg_certificate'] && $data['ifsg_certificate_light'];
}
$user->license->ifsg_certificate = (bool) $data['ifsg_certificate'];
$user->license->ifsg_confirmed = $data['ifsg_confirmed']
&& ($user->license->ifsg_certificate || $user->license->ifsg_certificate_light);
$user->license->save();
$this->addNotification('settings.certificates.success');
$this->log->info('Certificate "{certificate}" of user {user} ({id}) is {confirmation}.', [
'certificate' => $user->license->ifsg_certificate_light
? 'IfSG light'
: ($user->license->ifsg_certificate
? 'IfSG'
: 'no IfSG'
),
'user' => $user->name,
'id' => $user->id,
'confirmation' => $user->license->ifsg_confirmed ? 'confirmed' : 'unconfirmed',
]);
return $this->redirect->to('/users/' . $user->id . '/certificates');
}
public function settingsMenu(User $user): array
{
$menu = [
url('/users', ['action' => 'view', 'user_id' => $user->id]) => [
'title' => 'general.back', 'icon' => 'chevron-left',
],
];
if (config('ifsg_enabled')) {
$menu[url('/users/' . $user->id . '/certificates')] = [
'title' => 'settings.certificates',
'icon' => 'card-checklist',
'permission' => $this->isIfsgSupporter() ? null : 'user.ifsg.edit',
];
}
return $menu;
}
protected function checkPermission(string | array $abilities, bool $overwrite = false): void
{
if (!$overwrite && !$this->auth->can($abilities)) {
throw new HttpForbidden();
}
}
protected function view(User $user, string $view, array $data = []): Response
{
return $this->response->withView(
$view,
array_merge([
'settings_menu' => $this->settingsMenu($user),
'is_admin' => true,
'admin_user' => $user,
], $data)
);
}
protected function getUser(Request $request): User
{
$userId = $request->getAttribute('user_id');
return User::findOrFail($userId);
}
public function isIfsgSupporter(): bool
{
return (bool) AngelType::whereRequiresIfsgCertificate(true)
->leftJoin('user_angel_type', 'user_angel_type.angel_type_id', 'angel_types.id')
->where('user_angel_type.user_id', $this->auth->user()?->id)
->where('user_angel_type.supporter', true)
->count();
}
}

View File

@ -242,12 +242,11 @@ class SettingsController extends BaseController
public function certificate(): Response public function certificate(): Response
{ {
$user = $this->auth->user();
if (!config('ifsg_enabled') && !$this->checkDrivingLicense()) { if (!config('ifsg_enabled') && !$this->checkDrivingLicense()) {
throw new HttpNotFound(); throw new HttpNotFound();
} }
$user = $this->auth->user();
return $this->response->withView( return $this->response->withView(
'pages/settings/certificates', 'pages/settings/certificates',
[ [
@ -260,11 +259,11 @@ class SettingsController extends BaseController
public function saveIfsgCertificate(Request $request): Response public function saveIfsgCertificate(Request $request): Response
{ {
if (!config('ifsg_enabled')) { $user = $this->auth->user();
if (!config('ifsg_enabled') || $user->license->ifsg_confirmed) {
throw new HttpNotFound(); throw new HttpNotFound();
} }
$user = $this->auth->user();
$data = $this->validate($request, [ $data = $this->validate($request, [
'ifsg_certificate_light' => 'optional|checked', 'ifsg_certificate_light' => 'optional|checked',
'ifsg_certificate' => 'optional|checked', 'ifsg_certificate' => 'optional|checked',
@ -274,8 +273,8 @@ class SettingsController extends BaseController
$user->license->ifsg_certificate_light = !$data['ifsg_certificate'] && $data['ifsg_certificate_light']; $user->license->ifsg_certificate_light = !$data['ifsg_certificate'] && $data['ifsg_certificate_light'];
} }
$user->license->ifsg_certificate = (bool) $data['ifsg_certificate']; $user->license->ifsg_certificate = (bool) $data['ifsg_certificate'];
$user->license->save();
$user->license->save();
$this->addNotification('settings.certificates.success'); $this->addNotification('settings.certificates.success');
return $this->redirect->to('/settings/certificates'); return $this->redirect->to('/settings/certificates');

View File

@ -42,6 +42,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @method static QueryBuilder|AngelType[] whereContactEmail($value) * @method static QueryBuilder|AngelType[] whereContactEmail($value)
* @method static QueryBuilder|AngelType[] whereRestricted($value) * @method static QueryBuilder|AngelType[] whereRestricted($value)
* @method static QueryBuilder|AngelType[] whereRequiresDriverLicense($value) * @method static QueryBuilder|AngelType[] whereRequiresDriverLicense($value)
* @method static QueryBuilder|AngelType[] whereRequiresIfsgCertificate($value)
* @method static QueryBuilder|AngelType[] whereNoSelfSignup($value) * @method static QueryBuilder|AngelType[] whereNoSelfSignup($value)
* @method static QueryBuilder|AngelType[] whereShowOnDashboard($value) * @method static QueryBuilder|AngelType[] whereShowOnDashboard($value)
* @method static QueryBuilder|AngelType[] whereHideRegister($value) * @method static QueryBuilder|AngelType[] whereHideRegister($value)

View File

@ -16,6 +16,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @property bool $drive_12t * @property bool $drive_12t
* @property bool $ifsg_certificate_light * @property bool $ifsg_certificate_light
* @property bool $ifsg_certificate * @property bool $ifsg_certificate
* @property bool $ifsg_confirmed
* *
* @method static QueryBuilder|License[] whereHasCar($value) * @method static QueryBuilder|License[] whereHasCar($value)
* @method static QueryBuilder|License[] whereDriveForklift($value) * @method static QueryBuilder|License[] whereDriveForklift($value)
@ -25,6 +26,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @method static QueryBuilder|License[] whereDrive12T($value) * @method static QueryBuilder|License[] whereDrive12T($value)
* @method static QueryBuilder|License[] whereIfsgCertificateLight($value) * @method static QueryBuilder|License[] whereIfsgCertificateLight($value)
* @method static QueryBuilder|License[] whereIfsgCertificate($value) * @method static QueryBuilder|License[] whereIfsgCertificate($value)
* @method static QueryBuilder|License[] whereIfsgConfirmed($value)
*/ */
class License extends HasUserModel class License extends HasUserModel
{ {
@ -43,6 +45,7 @@ class License extends HasUserModel
'drive_12t' => false, 'drive_12t' => false,
'ifsg_certificate_light' => false, 'ifsg_certificate_light' => false,
'ifsg_certificate' => false, 'ifsg_certificate' => false,
'ifsg_confirmed' => false,
]; ];
/** /**
@ -60,6 +63,7 @@ class License extends HasUserModel
'drive_12t', 'drive_12t',
'ifsg_certificate_light', 'ifsg_certificate_light',
'ifsg_certificate', 'ifsg_certificate',
'ifsg_confirmed',
]; ];
/** @var array<string> */ /** @var array<string> */
@ -72,6 +76,7 @@ class License extends HasUserModel
'drive_12t' => 'boolean', 'drive_12t' => 'boolean',
'ifsg_certificate_light' => 'boolean', 'ifsg_certificate_light' => 'boolean',
'ifsg_certificate' => 'boolean', 'ifsg_certificate' => 'boolean',
'ifsg_confirmed' => 'boolean',
]; ];
/** /**

View File

@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Controllers\Admin\UserSettingsController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\License;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\ControllerTest;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\MockObject\MockObject;
class UserSettingsControllerTest extends ControllerTest
{
use HasDatabase;
protected Authenticator | MockObject $auth;
protected User $user;
protected User $userChanged;
protected UserSettingsController $controller;
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::certificate
*/
public function testCertificateDisabled(): void
{
config(['ifsg_enabled' => false]);
$this->expectException(HttpNotFound::class);
$this->controller->certificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::checkPermission
*/
public function testCertificateNotAllowed(): void
{
config(['ifsg_enabled' => true]);
$this->expectException(HttpForbidden::class);
$this->controller->certificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::__construct
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::certificate
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::checkPermission
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::getUser
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::view
*/
public function testCertificateByPermission(): void
{
config(['ifsg_enabled' => true]);
$this->setExpects($this->auth, 'can', ['user.ifsg.edit'], true, $this->atLeastOnce());
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data): Response {
$this->assertArrayHasKey('certificates', $data);
$this->assertArrayHasKey('settings_menu', $data);
$this->assertArrayHasKey('is_admin', $data);
$this->assertTrue($data['is_admin']);
$this->assertArrayHasKey('admin_user', $data);
$this->assertEquals($this->userChanged->id, $data['admin_user']->id);
return $this->response;
});
$this->controller->certificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::certificate
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::checkPermission
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::isIfsgSupporter
*/
public function testCertificateByAngelTypeSupporter(): void
{
config(['ifsg_enabled' => true]);
$this->setExpects($this->response, 'withView', null, $this->response);
$angelType = AngelType::factory()->create(['requires_ifsg_certificate' => true]);
$this->user->userAngelTypes()->attach($angelType, ['supporter' => true]);
$this->controller->certificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::saveIfsgCertificate
*/
public function testSaveIfsgCertificateDisabled(): void
{
config(['ifsg_enabled' => false]);
$this->expectException(HttpNotFound::class);
$this->controller->saveIfsgCertificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::saveIfsgCertificate
*/
public function testSaveIfsgCertificateNotAllowed(): void
{
config(['ifsg_enabled' => true]);
$this->expectException(HttpForbidden::class);
$this->controller->saveIfsgCertificate($this->request);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::saveIfsgCertificate
*/
public function testSaveIfsgCertificateConfirmed(): void
{
config(['ifsg_enabled' => true]);
$this->setExpects($this->auth, 'can', ['user.ifsg.edit'], true, $this->atLeastOnce());
$body = [
'ifsg_certificate' => true,
'ifsg_confirmed' => true,
];
$this->request = $this->request->withParsedBody($body);
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/users/' . $this->userChanged->id . '/certificates')
->willReturn($this->response);
$this->controller->saveIfsgCertificate($this->request);
$this->assertTrue($this->log->hasInfoThatContains('Certificate'));
$this->assertFalse($this->userChanged->license->ifsg_certificate_light);
$this->assertTrue($this->userChanged->license->ifsg_certificate);
$this->assertTrue($this->userChanged->license->ifsg_confirmed);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::saveIfsgCertificate
*/
public function testSaveIfsgCertificate(): void
{
config(['ifsg_enabled' => true]);
$this->setExpects($this->auth, 'can', ['user.ifsg.edit'], true, $this->atLeastOnce());
$body = [
'ifsg_certificate' => true,
];
$this->request = $this->request->withParsedBody($body);
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/users/' . $this->userChanged->id . '/certificates')
->willReturn($this->response);
$this->controller->saveIfsgCertificate($this->request);
$this->assertFalse($this->userChanged->license->ifsg_certificate_light);
$this->assertTrue($this->userChanged->license->ifsg_certificate);
$this->assertFalse($this->userChanged->license->ifsg_confirmed);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::saveIfsgCertificate
*/
public function testSaveIfsgCertificateLite(): void
{
config(['ifsg_enabled' => true, 'ifsg_light_enabled' => true]);
$this->setExpects($this->auth, 'can', ['user.ifsg.edit'], true, $this->atLeastOnce());
$body = [
'ifsg_certificate_light' => true,
];
$this->request = $this->request->withParsedBody($body);
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/users/' . $this->userChanged->id . '/certificates')
->willReturn($this->response);
$this->controller->saveIfsgCertificate($this->request);
$this->assertTrue($this->userChanged->license->ifsg_certificate_light);
$this->assertFalse($this->userChanged->license->ifsg_certificate);
$this->assertFalse($this->userChanged->license->ifsg_confirmed);
}
/**
* @covers \Engelsystem\Controllers\Admin\UserSettingsController::settingsMenu
*/
public function testSettingsMenu(): void
{
$menu = $this->controller->settingsMenu($this->userChanged);
$this->assertArrayHasKey('http://localhost/users?action=view&user_id=' . $this->userChanged->id, $menu);
config(['ifsg_enabled' => true]);
$menu = $this->controller->settingsMenu($this->userChanged);
$this->assertArrayHasKey('http://localhost/users/' . $this->userChanged->id . '/certificates', $menu);
}
/**
* Setup environment
*/
public function setUp(): void
{
parent::setUp();
$this->app->bind('http.urlGenerator', UrlGenerator::class);
$this->user = User::factory()->create();
$this->userChanged = User::factory()
->has(License::factory())
->create();
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);
$this->setExpects($this->auth, 'user', null, $this->user, $this->any());
$this->request = $this->request->withAttribute('user_id', $this->userChanged->id);
$this->controller = $this->app->make(UserSettingsController::class);
$this->controller->setValidator(new Validator());
}
}