diff --git a/config/config.default.php b/config/config.default.php index bee87e45..e9b413c6 100644 --- a/config/config.default.php +++ b/config/config.default.php @@ -364,6 +364,9 @@ return [ 'voucher_start' => env('VOUCHER_START', null) ?: null, ], + // Enables Driving License + 'driving_license_enabled' => (bool) env('DRIVING_LICENSE_ENABLED', true), + # Instruction in accordance with § 43 Para. 1 of the German Infection Protection Act (IfSG) 'ifsg_enabled' => (bool) env('IFSG_ENABLED', false), diff --git a/config/routes.php b/config/routes.php index 0e8bcdbc..b1322eb3 100644 --- a/config/routes.php +++ b/config/routes.php @@ -57,6 +57,7 @@ $route->addGroup( function (RouteCollector $route): void { $route->get('/certificates', 'Admin\\UserSettingsController@certificate'); $route->post('/certificates/ifsg', 'Admin\\UserSettingsController@saveIfsgCertificate'); + $route->post('/certificates/driving', 'Admin\\UserSettingsController@saveDrivingLicense'); } ); diff --git a/db/factories/User/LicenseFactory.php b/db/factories/User/LicenseFactory.php index 19f0c7e8..ad4e4874 100644 --- a/db/factories/User/LicenseFactory.php +++ b/db/factories/User/LicenseFactory.php @@ -21,6 +21,13 @@ class LicenseFactory extends Factory $drive_12t = $drive_7_5t && $this->faker->boolean(.3); $drive_forklift = ($drive_car && $this->faker->boolean(.1)) || ($drive_12t && $this->faker->boolean(.7)); + $drive_confirmed = $this->faker->boolean(0.5) && ( + $drive_car + || $drive_3_5t + || $drive_7_5t + || $drive_12t + || $drive_forklift + ); $ifsg_certificate = $this->faker->boolean(0.1); $ifsg_certificate_light = $this->faker->boolean(0.5) && !$ifsg_certificate; @@ -34,6 +41,7 @@ class LicenseFactory extends Factory 'drive_3_5t' => $drive_3_5t, 'drive_7_5t' => $drive_7_5t, 'drive_12t' => $drive_12t, + 'drive_confirmed' => $drive_confirmed, 'ifsg_certificate' => $ifsg_certificate, 'ifsg_certificate_light' => $ifsg_certificate_light, 'ifsg_confirmed' => $ifsg_confirmed, diff --git a/db/migrations/2024_03_13_000000_add_drive_confirmed_to_users_licenses.php b/db/migrations/2024_03_13_000000_add_drive_confirmed_to_users_licenses.php new file mode 100644 index 00000000..cf6858e9 --- /dev/null +++ b/db/migrations/2024_03_13_000000_add_drive_confirmed_to_users_licenses.php @@ -0,0 +1,31 @@ +schema->table('users_licenses', function (Blueprint $table): void { + $table->boolean('drive_confirmed')->default(false)->after('drive_12t'); + }); + } + + /** + * Reverse the migration + */ + public function down(): void + { + $this->schema->table('users_licenses', function (Blueprint $table): void { + $table->dropColumn('drive_confirmed'); + }); + } +} diff --git a/db/migrations/2024_03_13_000001_add_user_drive_edit_permission.php b/db/migrations/2024_03_13_000001_add_user_drive_edit_permission.php new file mode 100644 index 00000000..6fc4a9da --- /dev/null +++ b/db/migrations/2024_03_13_000001_add_user_drive_edit_permission.php @@ -0,0 +1,46 @@ +schema->getConnection(); + $db->table('privileges') + ->insert([ + 'name' => 'user.drive.edit', 'description' => 'Edit Driving License', + ]); + + $editDrive = $db->table('privileges') + ->where('name', 'user.drive.edit') + ->get(['id']) + ->first(); + + $shico = 60; + $team_coordinator = 65; + $db->table('group_privileges') + ->insertOrIgnore([ + ['group_id' => $shico, 'privilege_id' => $editDrive->id], + ['group_id' => $team_coordinator, 'privilege_id' => $editDrive->id], + ]); + } + + /** + * Reverse the migration + */ + public function down(): void + { + $db = $this->schema->getConnection(); + $db->table('privileges') + ->where('name', 'user.drive.edit') + ->delete(); + } +} diff --git a/includes/controller/angeltypes_controller.php b/includes/controller/angeltypes_controller.php index 89652a25..37567508 100644 --- a/includes/controller/angeltypes_controller.php +++ b/includes/controller/angeltypes_controller.php @@ -140,8 +140,12 @@ function angeltype_edit_controller() engelsystem_log( 'Saved angeltype: ' . $angeltype->name . ($angeltype->restricted ? ', restricted' : '') . ($angeltype->shift_self_signup ? ', shift_self_signup' : '') - . ($angeltype->requires_driver_license ? ', requires driver license' : '') . ', ' - . ($angeltype->requires_ifsg_certificate ? ', requires ifsg certificate' : '') . ', ' + . (config('driving_license_enabled') + ? (($angeltype->requires_driver_license ? ', requires driver license' : '') . ', ') + : '') + . (config('ifsg_enabled') + ? (($angeltype->requires_ifsg_certificate ? ', requires ifsg certificate' : '') . ', ') + : '') . $angeltype->contact_name . ', ' . $angeltype->contact_dect . ', ' . $angeltype->contact_email . ', ' diff --git a/includes/controller/users_controller.php b/includes/controller/users_controller.php index 6f7b900b..7e8259f7 100644 --- a/includes/controller/users_controller.php +++ b/includes/controller/users_controller.php @@ -254,6 +254,12 @@ function user_controller() ->where('user_angel_type.supporter', true) ->count(); + $is_drive_supporter = (bool) AngelType::whereRequiresDriverLicense(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( @@ -268,7 +274,10 @@ function user_controller() auth()->can('admin_active'), auth()->can('admin_user_worklog'), $worklogs, - auth()->can('user.ifsg.edit') || $is_ifsg_supporter, + auth()->can('user.ifsg.edit') + || $is_ifsg_supporter + || auth()->can('user.drive.edit') + || $is_drive_supporter, ), ]; } @@ -463,7 +472,7 @@ function user_driver_license_required_hint() $user = auth()->user(); // User has already entered data, no hint needed. - if ($user->license->wantsToDrive()) { + if (!config('driving_license_enabled') || $user->license->wantsToDrive()) { return null; } diff --git a/includes/view/AngelTypes_view.php b/includes/view/AngelTypes_view.php index 1bdc3880..1c8afc25 100644 --- a/includes/view/AngelTypes_view.php +++ b/includes/view/AngelTypes_view.php @@ -83,9 +83,39 @@ function AngelType_delete_view(AngelType $angeltype) */ function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode) { + $requires_ifsg = ''; + $requires_driving_license = ''; + if (config('ifsg_enabled')) { + $requires_ifsg = $supporter_mode ? + form_info( + __('angeltype.ifsg.required'), + $angeltype->requires_ifsg_certificate + ? __('Yes') + : __('No') + ) : form_checkbox( + 'requires_ifsg_certificate', + __('angeltype.ifsg.required'), + $angeltype->requires_ifsg_certificate + ); + } + if (config('driving_license_enabled')) { + $requires_driving_license = $supporter_mode ? + form_info( + __('Requires driver license'), + $angeltype->requires_driver_license + ? __('Yes') + : __('No') + ) : form_checkbox( + 'requires_driver_license', + __('Requires driver license'), + $angeltype->requires_driver_license + ); + } + $link = button($angeltype->id ? url('/angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype->id]) : url('/angeltypes'), icon('chevron-left'), 'btn-sm', '', __('general.back')); + return page_with_title( $link . ' ' . ( $angeltype->id ? @@ -120,30 +150,8 @@ function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode) __('angeltypes.shift.self_signup.info') . '">', $angeltype->shift_self_signup ), - $supporter_mode ? - form_info( - __('Requires driver license'), - $angeltype->requires_driver_license - ? __('Yes') - : __('No') - ) : - form_checkbox( - 'requires_driver_license', - __('Requires driver license'), - $angeltype->requires_driver_license - ), - $supporter_mode && config('ifsg_enabled') ? - form_info( - __('angeltype.ifsg.required'), - $angeltype->requires_ifsg_certificate - ? __('Yes') - : __('No') - ) : - form_checkbox( - 'requires_ifsg_certificate', - __('angeltype.ifsg.required'), - $angeltype->requires_ifsg_certificate - ), + $requires_driving_license, + $requires_ifsg, $supporter_mode ? form_info(__('Show on dashboard'), $angeltype->show_on_dashboard ? __('Yes') : __('No')) : form_checkbox('show_on_dashboard', __('Show on dashboard'), $angeltype->show_on_dashboard), @@ -182,7 +190,7 @@ function AngelType_edit_view(AngelType $angeltype, bool $supporter_mode) * @param UserAngelType|null $user_angeltype * @param bool $admin_angeltypes * @param bool $supporter - * @param License $user_driver_license + * @param License $user_license * @param User|null $user * @return string */ @@ -191,10 +199,10 @@ function AngelType_view_buttons( ?UserAngelType $user_angeltype, $admin_angeltypes, $supporter, - $user_driver_license, + $user_license, $user ) { - if ($angeltype->requires_driver_license) { + if (config('driving_license_enabled') && $angeltype->requires_driver_license) { $buttons[] = button( url('/settings/certificates'), icon('person-vcard') . __('My driving license') @@ -216,7 +224,7 @@ function AngelType_view_buttons( ($admin_angeltypes ? 'Join' : ''), ); } else { - if ($angeltype->requires_driver_license && !$user_driver_license->wantsToDrive()) { + if (config('driving_license_enabled') && $angeltype->requires_driver_license && !$user_license->wantsToDrive()) { error(__('This angeltype requires a driver license. Please enter your driver license information!')); } @@ -265,6 +273,13 @@ function AngelType_view_buttons( return buttons($buttons); } +function certificateIcon($confirmed, $certificate) +{ + return ($confirmed && $certificate) + ? icon('check2-all', 'text-success') + : icon_bool($certificate); +} + /** * Renders and sorts the members of an angeltype into supporters, members and unconfirmed members. * @@ -284,32 +299,36 @@ function AngelType_view_members(AngelType $angeltype, $members, $admin_user_ange if (config('enable_dect')) { $member['dect'] = htmlspecialchars((string) $member->contact->dect); } - if ($angeltype->requires_driver_license) { - $member['wants_to_drive'] = icon_bool($member->license->wantsToDrive()); + if (config('driving_license_enabled') && $angeltype->requires_driver_license) { + $drive_confirmed = $member->license->drive_confirmed; + $member['wants_to_drive'] = certificateIcon($drive_confirmed, $member->license->wantsToDrive()); $member['has_car'] = icon_bool($member->license->has_car); - $member['has_license_car'] = icon_bool($member->license->drive_car); - $member['has_license_3_5t_transporter'] = icon_bool($member->license->drive_3_5t); - $member['has_license_7_5t_truck'] = icon_bool($member->license->drive_7_5t); - $member['has_license_12t_truck'] = icon_bool($member->license->drive_12t); - $member['has_license_forklift'] = icon_bool($member->license->drive_forklift); + $member['has_license_car'] = certificateIcon($drive_confirmed, $member->license->drive_car); + $member['has_license_3_5t_transporter'] = certificateIcon($drive_confirmed, $member->license->drive_3_5t); + $member['has_license_7_5t_truck'] = certificateIcon($drive_confirmed, $member->license->drive_7_5t); + $member['has_license_12t_truck'] = certificateIcon($drive_confirmed, $member->license->drive_12t); + $member['has_license_forklift'] = certificateIcon($drive_confirmed, $member->license->drive_forklift); } - if ($angeltype->requires_ifsg_certificate && config('ifsg_enabled')) { - $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_enabled') && $angeltype->requires_ifsg_certificate) { + $ifsg_confirmed = $member->license->ifsg_confirmed; + $member['ifsg_certificate'] = certificateIcon($ifsg_confirmed, $member->license->ifsg_certificate); if (config('ifsg_light_enabled')) { - $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); + $member['ifsg_certificate_light'] = certificateIcon($ifsg_confirmed, $member->license->ifsg_certificate_light); } } $edit_certificates = ''; if ( - ($angeltype->requires_driver_license || $angeltype->requires_ifsg_certificate) - && ($admin_user_angeltypes || auth()->can('user.ifsg.edit')) + ( + config('driving_license_enabled') + && $angeltype->requires_driver_license + && ($admin_user_angeltypes || auth()->can('user.drive.edit')) + ) + || ( + config('ifsg_enabled') + && $angeltype->requires_ifsg_certificate + && ($admin_user_angeltypes || auth()->can('user.ifsg.edit')) + ) ) { $edit_certificates = button( @@ -425,7 +444,10 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a $headers['dect'] = __('general.dect'); } - if ($angeltype->requires_driver_license && ($supporter || $admin_angeltypes)) { + if ( + config('driving_license_enabled') && $angeltype->requires_driver_license + && ($supporter || $admin_angeltypes || auth()->can('user.drive.edit')) + ) { $headers = array_merge($headers, [ 'wants_to_drive' => __('Driver'), 'has_car' => __('Has car'), @@ -461,7 +483,7 @@ function AngelType_view_table_headers(AngelType $angeltype, $supporter, $admin_a * @param bool $admin_user_angeltypes * @param bool $admin_angeltypes * @param bool $supporter - * @param License $user_driver_license + * @param License $user_license * @param User $user * @param ShiftsFilterRenderer $shiftsFilterRenderer * @param ShiftCalendarRenderer $shiftCalendarRenderer @@ -475,7 +497,7 @@ function AngelType_view( $admin_user_angeltypes, $admin_angeltypes, $supporter, - $user_driver_license, + $user_license, $user, ShiftsFilterRenderer $shiftsFilterRenderer, ShiftCalendarRenderer $shiftCalendarRenderer, @@ -485,7 +507,7 @@ function AngelType_view( return page_with_title( $link . ' ' . sprintf(__('Team %s'), htmlspecialchars($angeltype->name)), [ - AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_driver_license, $user), + AngelType_view_buttons($angeltype, $user_angeltype, $admin_angeltypes, $supporter, $user_license, $user), msg(), tabs([ __('Info') => AngelType_view_info( diff --git a/includes/view/User_view.php b/includes/view/User_view.php index 27fa6197..84e9f4b7 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -553,7 +553,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 + * @param bool $admin_certificates * * @return string */ @@ -569,7 +569,7 @@ function User_view( $tshirt_admin, $admin_user_worklog_privilege, $user_worklogs, - $admin_ifsg + $admin_certificates ) { $goodie = GoodieType::from(config('goodie_type')); $goodie_enabled = $goodie !== GoodieType::None; @@ -653,7 +653,10 @@ function User_view( icon('valentine') . __('Vouchers') ) : '', - $admin_ifsg ? button( + ( + $admin_certificates + && (config('ifsg_enabled') || config('driving_license_enabled')) + ) ? button( url('/users/' . $user_source->id . '/certificates'), icon('card-checklist') . __('settings.certificates') ) : '', diff --git a/resources/lang/de_DE/default.po b/resources/lang/de_DE/default.po index 176e567e..bbfac6a1 100644 --- a/resources/lang/de_DE/default.po +++ b/resources/lang/de_DE/default.po @@ -1552,7 +1552,7 @@ msgid "news.comments.delete.title" msgstr "Kommentar \"%s\" löschen" msgid "notification.news.updated.introduction" -msgstr "Die News %1$s wurde aktualisiert" +msgstr "Die News \"%1$s\" wurde aktualisiert" msgid "notification.news.updated.text" msgstr "Du kannst sie dir unter %3$s anschauen." @@ -1754,6 +1754,9 @@ 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.drive_confirmed.hint" +msgstr "Dein Führerschein 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." diff --git a/resources/lang/en_US/default.po b/resources/lang/en_US/default.po index 0b8371fc..9c1a8889 100644 --- a/resources/lang/en_US/default.po +++ b/resources/lang/en_US/default.po @@ -254,7 +254,7 @@ msgid "news.comments.delete.title" msgstr "Delete comment \"%s\"" msgid "notification.news.updated.introduction" -msgstr "The news %1$s was updated" +msgstr "The news \"%1$s\" was updated" msgid "notification.news.updated.text" msgstr "You can view it at %3$s" @@ -456,6 +456,9 @@ 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.drive_confirmed.hint" +msgstr "Your driving license 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." diff --git a/resources/views/pages/settings/certificates-admin.twig b/resources/views/pages/settings/certificates-admin.twig index f993f97b..a61c754c 100644 --- a/resources/views/pages/settings/certificates-admin.twig +++ b/resources/views/pages/settings/certificates-admin.twig @@ -38,6 +38,47 @@ {% endif %} + {% if config('driving_license_enabled') %} +
+ {% endif %} {% endblock %} diff --git a/resources/views/pages/settings/certificates.twig b/resources/views/pages/settings/certificates.twig index 3fba994d..534d8868 100644 --- a/resources/views/pages/settings/certificates.twig +++ b/resources/views/pages/settings/certificates.twig @@ -6,7 +6,7 @@ {% block row_content %}