From d6899d37d92f8a85aa3a4b65810d193ddad541e7 Mon Sep 17 00:00:00 2001 From: frischler Date: Thu, 13 Oct 2022 20:23:22 +0200 Subject: [PATCH] Settings Modernization: Applying buildup and teardown time, add localization --- resources/lang/de_DE/additional.po | 11 +++ resources/lang/de_DE/default.po | 58 ++++++++++++ resources/lang/en_US/additional.po | 11 +++ resources/lang/en_US/default.po | 58 ++++++++++++ resources/views/macros/base.twig | 11 ++- resources/views/macros/form.twig | 14 ++- .../views/pages/password/reset-form.twig | 4 +- resources/views/pages/settings/password.twig | 4 +- resources/views/pages/settings/profile.twig | 47 ++++++---- .../ChecksArrivalsAndDepartures.php | 52 +++++++++++ src/Controllers/SettingsController.php | 25 ++++- .../ChecksArrivalsAndDeparturesTest.php | 92 +++++++++++++++++++ .../Controllers/SettingsControllerTest.php | 8 +- ...cksArrivalsAndDeparturesImplementation.php | 20 ++++ 14 files changed, 384 insertions(+), 31 deletions(-) create mode 100644 src/Controllers/ChecksArrivalsAndDepartures.php create mode 100644 tests/Unit/Controllers/ChecksArrivalsAndDeparturesTest.php create mode 100644 tests/Unit/Controllers/Stub/ChecksArrivalsAndDeparturesImplementation.php diff --git a/resources/lang/de_DE/additional.po b/resources/lang/de_DE/additional.po index 7b7df794..3045379d 100644 --- a/resources/lang/de_DE/additional.po +++ b/resources/lang/de_DE/additional.po @@ -110,6 +110,17 @@ msgstr "OAuth-Provider nicht gefunden" msgid "settings.profile" msgstr "Profil" +msgid "settings.profile.planned_arrival_date.invalid" +msgstr "Bitte gib Dein geplantes Ankunftsdatum an. " +"Es sollte nach dem Aufbaubeginn und vor dem Abbauende liegen." + +msgid "settings.profile.planned_departure_date.invalid" +msgstr "Bitte gib Dein geplantes Abreisedatum an, damit wir ein Gefühl für die Abbauplanung bekommen. " +"Es sollte nach dem Aufbaubeginn und vor dem Abbauende liegen." + +msgid "settings.profile.success" +msgstr "Einstellungen gespeichert." + msgid "faq.delete.success" msgstr "FAQ Eintrag erfolgreich gelöscht." diff --git a/resources/lang/de_DE/default.po b/resources/lang/de_DE/default.po index d677d1e6..0b37716e 100644 --- a/resources/lang/de_DE/default.po +++ b/resources/lang/de_DE/default.po @@ -2942,6 +2942,64 @@ msgstr "Nachricht" msgid "settings.settings" msgstr "Einstellungen" +msgid "settings.profile.user_details.info" +msgstr "Hier kannst Du Deine Details ändern." + +msgid "settings.profile.entry_required" +msgstr "Pflichtfeld!" + +msgid "settings.profile.nick" +msgstr "Nick" + +msgid "settings.profile.pronoun" +msgstr "Pronomen" + +msgid "settings.profile.pronoun.info" +msgstr "Wird auf deiner Profilseite und in Engellisten angezeigt." + +msgid "settings.profile.firstname" +msgstr "Vorname" + +msgid "settings.profile.lastname" +msgstr "Nachname" + +msgid "settings.profile.planned_arrival_date" +msgstr "Geplanter Ankunftstag" + +msgid "settings.profile.planned_departure_date" +msgstr "Geplanter Abreisetag" + +msgid "settings.profile.dect" +msgstr "DECT" + +msgid "settings.profile.mobile" +msgstr "Handy" + +msgid "settings.profile.email" +msgstr "E-Mail" + +msgid "settings.profile.email_shiftinfo" +msgstr "Das %s darf mir E-Mails senden (z.B. wenn sich meine Schichten ändern)." + +msgid "settings.profile.email_news" +msgstr "Benachrichtige mich bei neuen News." + +msgid "settings.profile.email_by_human_allowed" +msgstr "Erlaube Himmel-Engeln dich per Mail zu kontaktieren." + +msgid "settings.profile.email_goody" +msgstr "Um Voucher zu erhalten, stimme zu, dass Nick, E-Mail-Adresse, geleistete Arbeit und Shirtgröße " +"bis zum nächsten gleichartigen Event gespeichert werden." + +msgid "settings.profile.privacy" +msgstr "Dies kann jederzeit durch eine E-Mail an %1$s widerrufen werden." + +msgid "settings.profile.shirt_size" +msgstr "T-Shirt-Größe" + +msgid "settings.profile.angeltypes.info" +msgstr "Du kannst deine Engeltypen auf der Engeltypen-Seite verwalten." + msgid "settings.password" msgstr "Passwort" diff --git a/resources/lang/en_US/additional.po b/resources/lang/en_US/additional.po index ce25afc9..daab8b32 100644 --- a/resources/lang/en_US/additional.po +++ b/resources/lang/en_US/additional.po @@ -108,6 +108,17 @@ msgstr "Unable to find OAuth provider" msgid "settings.profile" msgstr "Profile" +msgid "settings.profile.planned_arrival_date.invalid" +msgstr "Please enter your planned date of arrival. " +"It should be after the buildup start date and before teardown end date." + +msgid "settings.profile.planned_departure_date.invalid" +msgstr "Please enter your planned date of departure. " +"It should be after your planned arrival date and after buildup start date and before teardown end date." + +msgid "settings.profile.success" +msgstr "Settings saved." + msgid "faq.delete.success" msgstr "FAQ entry successfully deleted." diff --git a/resources/lang/en_US/default.po b/resources/lang/en_US/default.po index be0f6a95..125f203b 100644 --- a/resources/lang/en_US/default.po +++ b/resources/lang/en_US/default.po @@ -208,6 +208,64 @@ msgstr "Message" msgid "settings.settings" msgstr "Settings" +msgid "settings.profile.user_details.info" +msgstr "Here you can change your user details." + +msgid "settings.profile.entry_required" +msgstr "Entry required!" + +msgid "settings.profile.nick" +msgstr "Nick" + +msgid "settings.profile.pronoun" +msgstr "Pronoun" + +msgid "settings.profile.pronoun.info" +msgstr "Will be shown on your profile page and in angel lists." + +msgid "settings.profile.firstname" +msgstr "First name" + +msgid "settings.profile.lastname" +msgstr "Last name" + +msgid "settings.profile.planned_arrival_date" +msgstr "Planned date of arrival" + +msgid "settings.profile.planned_departure_date" +msgstr "Planned date of departure" + +msgid "settings.profile.dect" +msgstr "DECT" + +msgid "settings.profile.mobile" +msgstr "Mobile" + +msgid "settings.profile.email" +msgstr "E-Mail" + +msgid "settings.profile.email_shiftinfo" +msgstr "The %s is allowed to send me an e-mail (e.g. when my shifts change)." + +msgid "settings.profile.email_news" +msgstr "Notify me of new news." + +msgid "settings.profile.email_by_human_allowed" +msgstr "Allow heaven angels to contact you by e-mail." + +msgid "settings.profile.email_goody" +msgstr "To receive vouchers, give consent that nick, e-mail address, worked hours and shirt size will be stored until " +"the next similar event." + +msgid "settings.profile.privacy" +msgstr "To withdraw your approval, send an e-mail to %1$s." + +msgid "settings.profile.shirt_size" +msgstr "Shirt size" + +msgid "settings.profile.angeltypes.info" +msgstr "You can manage your Angeltypes on the Angeltypes page." + msgid "settings.password" msgstr "Password" diff --git a/resources/views/macros/base.twig b/resources/views/macros/base.twig index dd8feff6..2ab1908d 100644 --- a/resources/views/macros/base.twig +++ b/resources/views/macros/base.twig @@ -36,8 +36,15 @@ {% endmacro %} -{% macro info(text) %} - {{ _self.icon('info-circle') }}{{ text }} +{% macro info(text, raw) %} + + {{ _self.icon('info-circle') }} + {%- if raw|default(false) -%} + {{ text|raw }} + {%- else -%} + {{ text }} + {%- endif -%} + {%- endmacro %} {% macro entry_required(text) %} diff --git a/resources/views/macros/form.twig b/resources/views/macros/form.twig index 346ebea3..66a2e244 100644 --- a/resources/views/macros/form.twig +++ b/resources/views/macros/form.twig @@ -12,8 +12,10 @@ type="{{ type|default('text') }}" class="form-control" id="{{ name }}" name="{{ name }}" value="{{ opt.value|default('')|escape('html_attr') }}" - {%- if opt.min is defined %} minlength="{{ opt.min }}"{% endif %} - {%- if opt.max is defined %} maxlength="{{ opt.max }}"{% endif %} + {%- if opt.min_length is defined %} minlength="{{ opt.min_length }}"{% endif %} + {%- if opt.max_length is defined %} maxlength="{{ opt.max_length }}"{% endif %} + {%- if opt.min is defined %} min="{{ opt.min }}"{% endif %} + {%- if opt.max is defined %} max="{{ opt.max }}"{% endif %} {%- if opt.required|default(false) %} required {%- endif -%} @@ -61,14 +63,18 @@ {%- endmacro %} -{% macro checkbox(name, label, checked, value, disabled) %} +{% macro checkbox(name, label, checked, value, disabled, raw_label) %}
{%- endmacro %} diff --git a/resources/views/pages/password/reset-form.twig b/resources/views/pages/password/reset-form.twig index e078ab0e..76ab2582 100644 --- a/resources/views/pages/password/reset-form.twig +++ b/resources/views/pages/password/reset-form.twig @@ -7,8 +7,8 @@
{{ csrf() }} - {{ f.input('password', __('Password'), 'password', {'min': min_length, 'required': true}) }} - {{ f.input('password_confirmation', __('Confirm password'), 'password', {'min': min_length, 'required': true}) }} + {{ f.input('password', __('Password'), 'password', {'min_length': min_length, 'required': true}) }} + {{ f.input('password_confirmation', __('Confirm password'), 'password', {'min_length': min_length, 'required': true}) }} {{ f.submit(__('Save')) }}
diff --git a/resources/views/pages/settings/password.twig b/resources/views/pages/settings/password.twig index ba7a864f..bb148f46 100644 --- a/resources/views/pages/settings/password.twig +++ b/resources/views/pages/settings/password.twig @@ -24,13 +24,13 @@ 'new_password', __('settings.password.new_password'), 'password', - {'min': min_length, 'required': true} + {'min_length': min_length, 'required': true} ) }} {{ f.input( 'new_password2', __('settings.password.new_password2'), 'password', - {'min': min_length, 'required': true} + {'min_length': min_length, 'required': true} ) }} {{ f.submit() }} diff --git a/resources/views/pages/settings/profile.twig b/resources/views/pages/settings/profile.twig index f3d964ba..ee56eda0 100644 --- a/resources/views/pages/settings/profile.twig +++ b/resources/views/pages/settings/profile.twig @@ -23,7 +23,7 @@ 'pronoun', __('settings.profile.pronoun'), 'text', - {'value': user.personalData.pronoun ,'max': 15} + {'value': user.personalData.pronoun ,'max_length': 15} ) }} {{ m.info(__('settings.profile.pronoun.info')) }} {% endif %} @@ -32,13 +32,13 @@ 'first_name', __('settings.profile.firstname'), 'text', - {'value': user.personalData.first_name, 'max': 64} + {'value': user.personalData.first_name, 'max_length': 64} ) }} {{ f.input( 'last_name', __('settings.profile.lastname'), 'text', - {'value': user.personalData.last_name, 'max': 64} + {'value': user.personalData.last_name, 'max_length': 64} ) }} {% endif %} @@ -50,16 +50,22 @@ __('settings.profile.planned_arrival_date'), 'date', { - 'value': user.personalData.planned_arrival_date.format(__('Y-m-d')), + 'value': user.personalData.planned_arrival_date.format('Y-m-d'), 'required': true, - 'entry_required_icon': true + 'entry_required_icon': true, + 'min': config('buildup_start') ? config('buildup_start').format('Y-m-d') : '', + 'max': config('teardown_end') ? config('teardown_end').format('Y-m-d') : '', } ) }} {{ f.input( 'planned_departure_date', __('settings.profile.planned_departure_date'), - 'text', - {'value': user.personalData.planned_departure_date.format(__('Y-m-d'))} + 'date', + { + 'value': user.personalData.planned_departure_date.format('Y-m-d'), + 'min': config('buildup_start') ? config('buildup_start').format('Y-m-d') : '', + 'max': config('teardown_end') ? config('teardown_end').format('Y-m-d') : '', + } ) }} {% endif %} @@ -67,30 +73,30 @@
{% if config('enable_dect') %} {{ f.input( - 'dect', - __('settings.profile.dect'), - 'text', - {'value': user.contact.dect, 'max': 40} + 'dect', + __('settings.profile.dect'), + 'text', + {'value': user.contact.dect, 'max_length': 40} ) }} {% endif %} {{ f.input( 'mobile', __('settings.profile.mobile'), 'text', - {'value': user.contact.mobile, 'max': 40} + {'value': user.contact.mobile, 'max_length': 40} ) }} {{ f.input( 'email', __('settings.profile.email'), 'email', - {'value': user.email, 'max': 254, 'required': true, 'entry_required_icon': true} + {'value': user.email, 'max_length': 254, 'required': true, 'entry_required_icon': true} ) }}
{{ f.checkbox( 'email_shiftinfo', - __('settings.profile.email_shiftinfo'), + __('settings.profile.email_shiftinfo', [config('app_name')]), user.settings.email_shiftinfo ) }} {{ f.checkbox( @@ -104,10 +110,17 @@ user.settings.email_human ) }} {% if config('enable_goody') %} + {% set privacy_email = config('privacy_email') %} + {% set email_goody_label = __('settings.profile.email_goody') ~ + (privacy_email ? ' ' ~ __('settings.profile.privacy', [privacy_email]) : '') + %} {{ f.checkbox( 'email_goody', - __('settings.profile.email_goody'), - user.settings.email_goody + email_goody_label, + user.settings.email_goody, + user.settings.email_goody, + false, + true ) }} {% endif %}
@@ -122,7 +135,7 @@
- {{ m.info(__('settings.profile.user_details.info')) }} + {{ m.info(__('settings.profile.angeltypes.info', [url('/angeltypes')]), true) }} {{ f.submit() }}
diff --git a/src/Controllers/ChecksArrivalsAndDepartures.php b/src/Controllers/ChecksArrivalsAndDepartures.php new file mode 100644 index 00000000..a1f211e4 --- /dev/null +++ b/src/Controllers/ChecksArrivalsAndDepartures.php @@ -0,0 +1,52 @@ +toCarbon($arrival); + + if (!is_null($departure) && $arrival_carbon->greaterThan($this->toCarbon($departure))) { + return false; + } + + return !$this->isBeforeBuildup($arrival_carbon) && !$this->isAfterTeardown($arrival_carbon); + } + + protected function isDepartureDateValid(?string $arrival, ?string $departure): bool + { + if (is_null($departure)) { + return true; // since optional value + } + $departure_carbon = $this->toCarbon($departure); + + return $departure_carbon->greaterThanOrEqualTo($this->toCarbon($arrival)) && + !$this->isBeforeBuildup($departure_carbon) && !$this->isAfterTeardown($departure_carbon); + } + + private function toCarbon(string $date_string): Carbon + { + return new Carbon(DateTime::createFromFormat('Y-m-d', $date_string)); + } + + private function isBeforeBuildup(Carbon $date): bool + { + $buildup = config('buildup_start'); + return !empty($buildup) && $date->lessThan($buildup->setTime(0,0)); + } + + private function isAfterTeardown(Carbon $date): bool + { + $teardown = config('teardown_end'); + return !empty($teardown) && $date->greaterThanOrEqualTo($teardown->addDay()->setTime(0,0)); + } +} diff --git a/src/Controllers/SettingsController.php b/src/Controllers/SettingsController.php index 59bcee46..777c8a6c 100644 --- a/src/Controllers/SettingsController.php +++ b/src/Controllers/SettingsController.php @@ -2,6 +2,7 @@ namespace Engelsystem\Controllers; +use Carbon\Carbon; use Engelsystem\Config\Config; use Engelsystem\Http\Exceptions\HttpNotFound; use Engelsystem\Http\Response; @@ -13,6 +14,7 @@ use Psr\Log\LoggerInterface; class SettingsController extends BaseController { use HasUserNotifications; + use ChecksArrivalsAndDepartures; /** @var Authenticator */ protected $auth; @@ -72,6 +74,10 @@ class SettingsController extends BaseController public function saveProfile(Request $request): Response { $user = $this->auth->user(); + + config('buildup_start'); + config('teardown_end'); + $data = $this->validate($request, [ 'pronoun' => 'optional|max:15', 'first_name' => 'optional|max:64', @@ -98,8 +104,18 @@ class SettingsController extends BaseController } if (config('enable_planned_arrival')) { - $user->personalData->planned_arrival_date = $data['planned_arrival_date']; - $user->personalData->planned_departure_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'); + return $this->redirect->to('/settings/profile'); + + } else if (!$this->isDepartureDateValid($data['planned_arrival_date'], $data['planned_departure_date'])) { + $this->addNotification('settings.profile.planned_departure_date.invalid', 'errors'); + return $this->redirect->to('/settings/profile'); + + } else { + $user->personalData->planned_arrival_date = $data['planned_arrival_date']; + $user->personalData->planned_departure_date = $data['planned_departure_date']; + } } if (config('enable_dect')) { @@ -116,7 +132,10 @@ class SettingsController extends BaseController $user->settings->email_goody = $data['email_goody']; } - $user->personalData->shirt_size = $data['shirt_size']; + if (isset(config('tshirt_sizes')[$data['shirt_size']])) { + $user->personalData->shirt_size = $data['shirt_size']; + $user->personalData->save(); + } $user->personalData->save(); $user->contact->save(); diff --git a/tests/Unit/Controllers/ChecksArrivalsAndDeparturesTest.php b/tests/Unit/Controllers/ChecksArrivalsAndDeparturesTest.php new file mode 100644 index 00000000..085b92bb --- /dev/null +++ b/tests/Unit/Controllers/ChecksArrivalsAndDeparturesTest.php @@ -0,0 +1,92 @@ + is_null($buildup) ? null: new Carbon($buildup)]); + config(['teardown_end' => is_null($teardown) ? null: new Carbon($teardown)]); + + $check = new ChecksArrivalsAndDeparturesImplementation(); + $this->assertFalse($check->checkArrival($arrival, $departure)); + } + + /** + * @covers \Engelsystem\Controllers\ChecksArrivalsAndDepartures::isDepartureDateValid + * @dataProvider invalidDateCombinations + */ + public function testCheckInvalidDatesForDeparture($buildup, $teardown, $arrival, $departure) + { + config(['buildup_start' => is_null($buildup) ? null: new Carbon($buildup)]); + config(['teardown_end' => is_null($teardown) ? null: new Carbon($teardown)]); + + $check = new ChecksArrivalsAndDeparturesImplementation(); + $this->assertFalse($check->checkDeparture($arrival, $departure)); + } + + /** + * @covers \Engelsystem\Controllers\ChecksArrivalsAndDepartures::isArrivalDateValid + * @dataProvider validDateCombinations + */ + public function testCheckValidDatesForArrival($buildup, $teardown, $arrival, $departure) + { + config(['buildup_start' => is_null($buildup) ? null: new Carbon($buildup)]); + config(['teardown_end' => is_null($teardown) ? null: new Carbon($teardown)]); + + $check = new ChecksArrivalsAndDeparturesImplementation(); + $this->assertTrue($check->checkArrival($arrival, $departure)); + } + + /** + * @covers \Engelsystem\Controllers\ChecksArrivalsAndDepartures::isDepartureDateValid + * @dataProvider validDateCombinations + */ + public function testCheckValidDatesForDeparture($buildup, $teardown, $arrival, $departure) + { + config(['buildup_start' => is_null($buildup) ? null: new Carbon($buildup)]); + config(['teardown_end' => is_null($teardown) ? null: new Carbon($teardown)]); + + $check = new ChecksArrivalsAndDeparturesImplementation(); + $this->assertTrue($check->checkDeparture($arrival, $departure)); + } + + public function setUp(): void + { + parent::setUp(); + $this->config = new Config(); + $this->app->instance('config', $this->config); + $this->app->instance(Config::class, $this->config); + } +} diff --git a/tests/Unit/Controllers/SettingsControllerTest.php b/tests/Unit/Controllers/SettingsControllerTest.php index 0563e3b9..b3ff8e30 100644 --- a/tests/Unit/Controllers/SettingsControllerTest.php +++ b/tests/Unit/Controllers/SettingsControllerTest.php @@ -613,7 +613,13 @@ class SettingsControllerTest extends TestCase 'en_US' => 'English', 'de_DE' => 'Deutsch' ]; - $this->config = new Config(['min_password_length' => 6, 'themes' => $themes, 'locales' => $languages]); + $tshirt_sizes = ['S' => 'Small']; + $this->config = new Config([ + 'min_password_length' => 6, + 'themes' => $themes, + 'locales' => $languages, + 'tshirt_sizes' => $tshirt_sizes + ]); $this->app->instance('config', $this->config); $this->app->instance(Config::class, $this->config); diff --git a/tests/Unit/Controllers/Stub/ChecksArrivalsAndDeparturesImplementation.php b/tests/Unit/Controllers/Stub/ChecksArrivalsAndDeparturesImplementation.php new file mode 100644 index 00000000..06465d7a --- /dev/null +++ b/tests/Unit/Controllers/Stub/ChecksArrivalsAndDeparturesImplementation.php @@ -0,0 +1,20 @@ +isArrivalDateValid($arrival, $departure); + } + + public function checkDeparture(string $arrival, string $departure): bool + { + return $this->isDepartureDateValid($arrival, $departure); + } +}