From 873803eb2df8bf88870e197f7fcd912ec2f4b4ab Mon Sep 17 00:00:00 2001 From: Xu Date: Tue, 5 Mar 2024 17:42:38 +0100 Subject: [PATCH] ifsg: can be confirmed and edited by admins --- config/routes.php | 9 + db/factories/User/LicenseFactory.php | 2 + ...0_add_ifsg_confirmed_to_users_licenses.php | 31 +++ ...5_000001_add_user_ifsg_edit_permission.php | 46 ++++ includes/controller/users_controller.php | 10 +- includes/view/AngelTypes_view.php | 44 +++- includes/view/UserAngelTypes_view.php | 4 +- includes/view/User_view.php | 8 +- resources/lang/de_DE/default.po | 22 +- resources/lang/en_US/default.po | 25 +- .../pages/settings/certificates-admin.twig | 43 ++++ .../views/pages/settings/certificates.twig | 21 +- resources/views/pages/settings/settings.twig | 21 +- .../Admin/UserSettingsController.php | 142 +++++++++++ src/Controllers/SettingsController.php | 15 +- src/Models/AngelType.php | 1 + src/Models/User/License.php | 33 +-- .../Admin/UserSettingsControllerTest.php | 235 ++++++++++++++++++ .../Controllers/SettingsControllerTest.php | 2 +- 19 files changed, 666 insertions(+), 48 deletions(-) create mode 100644 db/migrations/2024_03_05_000000_add_ifsg_confirmed_to_users_licenses.php create mode 100644 db/migrations/2024_03_05_000001_add_user_ifsg_edit_permission.php create mode 100644 resources/views/pages/settings/certificates-admin.twig create mode 100644 src/Controllers/Admin/UserSettingsController.php create mode 100644 tests/Unit/Controllers/Admin/UserSettingsControllerTest.php diff --git a/config/routes.php b/config/routes.php index 237e2a63..0e8bcdbc 100644 --- a/config/routes.php +++ b/config/routes.php @@ -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 $route->addGroup( '/password/reset', diff --git a/db/factories/User/LicenseFactory.php b/db/factories/User/LicenseFactory.php index 5240c971..19f0c7e8 100644 --- a/db/factories/User/LicenseFactory.php +++ b/db/factories/User/LicenseFactory.php @@ -24,6 +24,7 @@ class LicenseFactory extends Factory $ifsg_certificate = $this->faker->boolean(0.1); $ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate; + $ifsg_confirmed = $this->faker->boolean(0.5) && ($ifsg_certificate || $ifsg_certificate_light); return [ 'user_id' => User::factory(), @@ -35,6 +36,7 @@ class LicenseFactory extends Factory 'drive_12t' => $drive_12t, 'ifsg_certificate' => $ifsg_certificate, 'ifsg_certificate_light' => $ifsg_certificate_light, + 'ifsg_confirmed' => $ifsg_confirmed, ]; } } diff --git a/db/migrations/2024_03_05_000000_add_ifsg_confirmed_to_users_licenses.php b/db/migrations/2024_03_05_000000_add_ifsg_confirmed_to_users_licenses.php new file mode 100644 index 00000000..f4efb70e --- /dev/null +++ b/db/migrations/2024_03_05_000000_add_ifsg_confirmed_to_users_licenses.php @@ -0,0 +1,31 @@ +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'); + }); + } +} diff --git a/db/migrations/2024_03_05_000001_add_user_ifsg_edit_permission.php b/db/migrations/2024_03_05_000001_add_user_ifsg_edit_permission.php new file mode 100644 index 00000000..96ab3d4e --- /dev/null +++ b/db/migrations/2024_03_05_000001_add_user_ifsg_edit_permission.php @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/includes/controller/users_controller.php b/includes/controller/users_controller.php index c1fe0022..c6dab7fd 100644 --- a/includes/controller/users_controller.php +++ b/includes/controller/users_controller.php @@ -1,6 +1,7 @@ with(['user', 'creator']) ->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 [ htmlspecialchars($user_source->displayName), User_view( @@ -261,7 +268,8 @@ function user_controller() $tshirt_score, auth()->can('admin_active'), auth()->can('admin_user_worklog'), - $worklogs + $worklogs, + auth()->can('user.ifsg.edit') || $is_ifsg_supporter, ), ]; } diff --git a/includes/view/AngelTypes_view.php b/includes/view/AngelTypes_view.php index 78ae5fa1..1bdc3880 100644 --- a/includes/view/AngelTypes_view.php +++ b/includes/view/AngelTypes_view.php @@ -197,10 +197,10 @@ function AngelType_view_buttons( if ($angeltype->requires_driver_license) { $buttons[] = button( 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( url('/settings/certificates'), 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); } 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')) { - $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)) { $member['actions'] = table_buttons([ + $edit_certificates, button( url( '/user-angeltypes', @@ -323,6 +344,7 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange } elseif ($member->pivot->supporter) { if ($admin_angeltypes || ($admin_user_angeltypes && config('supporters_can_promote'))) { $member['actions'] = table_buttons([ + $edit_certificates, button( url('/user-angeltypes', [ 'action' => 'update', @@ -336,12 +358,15 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange ), ]); } else { - $member['actions'] = ''; + $member['actions'] = $edit_certificates + ? table_buttons([$edit_certificates,]) + : ''; } $supporters[] = $member; } else { if ($admin_user_angeltypes) { $member['actions'] = table_buttons([ + $edit_certificates, ($admin_angeltypes || config('supporters_can_promote')) ? button( url('/user-angeltypes', [ @@ -366,6 +391,10 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange __('Remove'), ), ]); + } elseif ($edit_certificates) { + $member['actions'] = table_buttons([ + $edit_certificates, + ]); } $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')) { $headers['ifsg_certificate_light'] = __('ifsg.certificate_light'); } diff --git a/includes/view/UserAngelTypes_view.php b/includes/view/UserAngelTypes_view.php index aa916256..d559d450 100644 --- a/includes/view/UserAngelTypes_view.php +++ b/includes/view/UserAngelTypes_view.php @@ -151,7 +151,9 @@ function UserAngelType_add_view(AngelType $angeltype, $users_source, $user_id) msg(), form([ 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_submit('submit', icon('plus-lg') . __('Add')), ]), diff --git a/includes/view/User_view.php b/includes/view/User_view.php index aef21678..73794ffd 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -532,6 +532,7 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege) * @param bool $tshirt_admin * @param bool $admin_user_worklog_privilege * @param Worklog[]|Collection $user_worklogs + * @param bool $admin_ifsg * * @return string */ @@ -546,7 +547,8 @@ function User_view( $tshirt_score, $tshirt_admin, $admin_user_worklog_privilege, - $user_worklogs + $user_worklogs, + $admin_ifsg ) { $goodie = GoodieType::from(config('goodie_type')); $goodie_enabled = $goodie !== GoodieType::None; @@ -629,6 +631,10 @@ function User_view( icon('valentine') . __('Vouchers') ) : '', + $admin_ifsg ? button( + url('/users/' . $user_source->id . '/certificates'), + icon('card-checklist') . __('settings.certificates') + ) : '', $admin_user_worklog_privilege ? button( url('/admin/user/' . $user_source->id . '/worklog'), icon('clock-history') . __('worklog.add') diff --git a/resources/lang/de_DE/default.po b/resources/lang/de_DE/default.po index 2e7f470c..4397b419 100644 --- a/resources/lang/de_DE/default.po +++ b/resources/lang/de_DE/default.po @@ -368,6 +368,9 @@ msgstr "Du darfst diesen Benutzer nicht von diesem Engeltyp entfernen." msgid "User %s removed from %s." msgstr "Benutzer %s von %s entfernt." +msgid "Edit certificates" +msgstr "Zertifikate bearbeiten" + msgid "Remove angeltype" msgstr "Engeltyp löschen" @@ -911,7 +914,7 @@ msgstr "Kontakt" msgid "Primary contact person/desk for user questions." msgstr "Ansprechpartner für Fragen." -msgid "my driving license" +msgid "My driving license" msgstr "Meine Führerschein-Infos" msgid "" @@ -1736,11 +1739,28 @@ msgstr "Gabelstapler" msgid "settings.certificates.ifsg_light" 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" 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. " "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" msgstr "Zertifikate wurden erfolgreich aktualisiert." diff --git a/resources/lang/en_US/default.po b/resources/lang/en_US/default.po index 4d3248b1..39ee1995 100644 --- a/resources/lang/en_US/default.po +++ b/resources/lang/en_US/default.po @@ -437,11 +437,28 @@ msgstr "Forklift" msgid "settings.certificates.ifsg_light" 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" 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." +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" msgstr "Certificates were updated successfully." @@ -449,13 +466,13 @@ msgid "angeltype.ifsg.required" msgstr "Requires health instruction" msgid "ifsg.certificate" -msgstr "health instruction" +msgstr "Health instruction" msgid "ifsg.certificate_light" -msgstr "health instruction on site" +msgstr "Health instruction on site" msgid "angeltype.ifsg.own" -msgstr "my health instruction" +msgstr "My health instruction" msgid "angeltype.ifsg.required.info" msgstr "This angeltype requires a health instruction. Please enter your health instruction information!" diff --git a/resources/views/pages/settings/certificates-admin.twig b/resources/views/pages/settings/certificates-admin.twig new file mode 100644 index 00000000..f993f97b --- /dev/null +++ b/resources/views/pages/settings/certificates-admin.twig @@ -0,0 +1,43 @@ +{% extends 'pages/settings/certificates.twig' %} +{% import 'macros/form.twig' as f %} +{% import 'macros/base.twig' as m %} + +{% block row_content %} +
+ + {% if config('ifsg_enabled') %} +
+ {{ csrf() }} +
+

{{ __('settings.certificates.title.ifsg') }}

+ + {% 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, + }) }} +
+
+ {{ f.submit(__('form.save'), {'icon_left': 'save'}) }} +
+ +
+ {{ f.checkbox( + 'ifsg_confirmed', + __('settings.certificates.confirmed') ~ f.info(__('settings.certificates.confirmation.info')), + {'raw_label': true, 'checked': certificates.ifsg_confirmed} + ) }} +
+
+
+
+ {% endif %} + +
+{% endblock %} diff --git a/resources/views/pages/settings/certificates.twig b/resources/views/pages/settings/certificates.twig index d0d1eb78..3fba994d 100644 --- a/resources/views/pages/settings/certificates.twig +++ b/resources/views/pages/settings/certificates.twig @@ -7,20 +7,33 @@ {% block row_content %}
{% if config('ifsg_enabled') %} -
+ {{ csrf() }}

{{ __('settings.certificates.title.ifsg') }}

{{ m.info(__('settings.certificates.info')) }} + {% if certificates.ifsg_confirmed %} +

{{ __('settings.certificates.ifsg_confirmed.hint') }}

+ {% endif %} + {% if config('ifsg_light_enabled') %} {{ f.checkbox('ifsg_certificate_light', __('settings.certificates.ifsg_light'), { - 'checked': certificates.ifsg_certificate_light, + 'checked': certificates.ifsg_certificate_light, + 'disabled': certificates.ifsg_confirmed, }) }} {% endif %} + {{ f.checkbox('ifsg_certificate', __('settings.certificates.ifsg'), { - 'checked': certificates.ifsg_certificate, + 'checked': certificates.ifsg_certificate, + 'disabled': certificates.ifsg_confirmed, }) }} - {{ f.submit(__('form.save'), {'icon_left': 'save'}) }} + + {% if not certificates.ifsg_confirmed %} + {{ f.submit(__('form.save'), {'icon_left': 'save'}) }} + {% endif %}
{% endif %} diff --git a/resources/views/pages/settings/settings.twig b/resources/views/pages/settings/settings.twig index 35034ee3..bf54bbb5 100644 --- a/resources/views/pages/settings/settings.twig +++ b/resources/views/pages/settings/settings.twig @@ -8,7 +8,12 @@ {% block container_title %}

{{ __('settings.settings') }} - {{ block('title') }} + + {{ block('title') }} + {% if is_admin|default(false) %} + ({{ admin_user.name }}) + {% endif %} +

{% endblock %} @@ -16,12 +21,14 @@
diff --git a/src/Controllers/Admin/UserSettingsController.php b/src/Controllers/Admin/UserSettingsController.php new file mode 100644 index 00000000..0b753f9e --- /dev/null +++ b/src/Controllers/Admin/UserSettingsController.php @@ -0,0 +1,142 @@ +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(); + } +} diff --git a/src/Controllers/SettingsController.php b/src/Controllers/SettingsController.php index a6fbc8e4..e0c9697b 100644 --- a/src/Controllers/SettingsController.php +++ b/src/Controllers/SettingsController.php @@ -242,29 +242,28 @@ class SettingsController extends BaseController public function certificate(): Response { - $user = $this->auth->user(); - if (!config('ifsg_enabled') && !$this->checkDrivingLicense()) { throw new HttpNotFound(); } + $user = $this->auth->user(); return $this->response->withView( 'pages/settings/certificates', [ - 'settings_menu' => $this->settingsMenu(), - 'driving_license' => $this->checkDrivingLicense(), - 'certificates' => $user->license, + 'settings_menu' => $this->settingsMenu(), + 'driving_license' => $this->checkDrivingLicense(), + 'certificates' => $user->license, ] ); } 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(); } - $user = $this->auth->user(); $data = $this->validate($request, [ 'ifsg_certificate_light' => '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 = (bool) $data['ifsg_certificate']; - $user->license->save(); + $user->license->save(); $this->addNotification('settings.certificates.success'); return $this->redirect->to('/settings/certificates'); diff --git a/src/Models/AngelType.php b/src/Models/AngelType.php index 3fdca538..1db06014 100644 --- a/src/Models/AngelType.php +++ b/src/Models/AngelType.php @@ -42,6 +42,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @method static QueryBuilder|AngelType[] whereContactEmail($value) * @method static QueryBuilder|AngelType[] whereRestricted($value) * @method static QueryBuilder|AngelType[] whereRequiresDriverLicense($value) + * @method static QueryBuilder|AngelType[] whereRequiresIfsgCertificate($value) * @method static QueryBuilder|AngelType[] whereNoSelfSignup($value) * @method static QueryBuilder|AngelType[] whereShowOnDashboard($value) * @method static QueryBuilder|AngelType[] whereHideRegister($value) diff --git a/src/Models/User/License.php b/src/Models/User/License.php index a25102bb..565ef292 100644 --- a/src/Models/User/License.php +++ b/src/Models/User/License.php @@ -16,6 +16,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @property bool $drive_12t * @property bool $ifsg_certificate_light * @property bool $ifsg_certificate + * @property bool $ifsg_confirmed * * @method static QueryBuilder|License[] whereHasCar($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[] whereIfsgCertificateLight($value) * @method static QueryBuilder|License[] whereIfsgCertificate($value) + * @method static QueryBuilder|License[] whereIfsgConfirmed($value) */ class License extends HasUserModel { @@ -35,14 +37,15 @@ class License extends HasUserModel /** @var array Default attributes */ protected $attributes = [ // phpcs:ignore - 'has_car' => false, - 'drive_forklift' => false, - 'drive_car' => false, - 'drive_3_5t' => false, - 'drive_7_5t' => false, - 'drive_12t' => false, + 'has_car' => false, + 'drive_forklift' => false, + 'drive_car' => false, + 'drive_3_5t' => false, + 'drive_7_5t' => false, + 'drive_12t' => false, 'ifsg_certificate_light' => false, - 'ifsg_certificate' => false, + 'ifsg_certificate' => false, + 'ifsg_confirmed' => false, ]; /** @@ -60,18 +63,20 @@ class License extends HasUserModel 'drive_12t', 'ifsg_certificate_light', 'ifsg_certificate', + 'ifsg_confirmed', ]; /** @var array */ protected $casts = [ // phpcs:ignore - 'has_car' => 'boolean', - 'drive_forklift' => 'boolean', - 'drive_car' => 'boolean', - 'drive_3_5t' => 'boolean', - 'drive_7_5t' => 'boolean', - 'drive_12t' => 'boolean', + 'has_car' => 'boolean', + 'drive_forklift' => 'boolean', + 'drive_car' => 'boolean', + 'drive_3_5t' => 'boolean', + 'drive_7_5t' => 'boolean', + 'drive_12t' => 'boolean', 'ifsg_certificate_light' => 'boolean', - 'ifsg_certificate' => 'boolean', + 'ifsg_certificate' => 'boolean', + 'ifsg_confirmed' => 'boolean', ]; /** diff --git a/tests/Unit/Controllers/Admin/UserSettingsControllerTest.php b/tests/Unit/Controllers/Admin/UserSettingsControllerTest.php new file mode 100644 index 00000000..96b9d42a --- /dev/null +++ b/tests/Unit/Controllers/Admin/UserSettingsControllerTest.php @@ -0,0 +1,235 @@ + 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()); + } +} diff --git a/tests/Unit/Controllers/SettingsControllerTest.php b/tests/Unit/Controllers/SettingsControllerTest.php index af961809..8aabac29 100644 --- a/tests/Unit/Controllers/SettingsControllerTest.php +++ b/tests/Unit/Controllers/SettingsControllerTest.php @@ -805,7 +805,7 @@ class SettingsControllerTest extends ControllerTest $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); $body = [ - 'ifsg_certificate' => true, + 'ifsg_certificate' => true, ]; $this->request = $this->request->withParsedBody($body);