From 28a4e70929b3ea13f65eb1861c39d132f8c5ba03 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Fri, 7 Nov 2025 14:34:26 +0100 Subject: [PATCH] Add reply to mail to prevention and events --- app/Form/FormSettings.php | 6 ++++-- app/Form/Mails/ConfirmRegistrationMail.php | 12 +++++++++++- app/Prevention/Actions/SettingStoreAction.php | 2 ++ app/Prevention/Mails/YearlyMail.php | 11 ++++++++++- app/Prevention/PreventionSettings.php | 2 ++ .../202511-07_create_replyto_mail_settings.php | 12 ++++++++++++ resources/js/views/setting/Form.vue | 1 + resources/js/views/setting/Prevention.vue | 1 + tests/EndToEnd/Member/PreventionTest.php | 11 +++++++++++ tests/Feature/Form/FormRegisterActionTest.php | 17 +++++++++++++++++ tests/Feature/Prevention/SettingTest.php | 4 ++++ 11 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 database/settings/202511-07_create_replyto_mail_settings.php diff --git a/app/Form/FormSettings.php b/app/Form/FormSettings.php index 9f4cdb57..67b3a375 100644 --- a/app/Form/FormSettings.php +++ b/app/Form/FormSettings.php @@ -2,7 +2,6 @@ namespace App\Form; -use App\Form\Actions\SettingStoreAction; use App\Setting\Contracts\Storeable; use App\Setting\LocalSettings; use Lorisleiva\Actions\ActionRequest; @@ -11,6 +10,7 @@ class FormSettings extends LocalSettings implements Storeable { public string $registerUrl; public string $clearCacheUrl; + public ?string $replyToMail; public static function group(): string { @@ -19,7 +19,7 @@ class FormSettings extends LocalSettings implements Storeable public static function title(): string { - return 'Formulare'; + return 'Veranstaltungen'; } /** @@ -30,6 +30,7 @@ class FormSettings extends LocalSettings implements Storeable return [ 'registerUrl' => 'present|string', 'clearCacheUrl' => 'present|string', + 'replyToMail' => 'nullable|string|email', ]; } @@ -47,6 +48,7 @@ class FormSettings extends LocalSettings implements Storeable 'data' => [ 'registerUrl' => $this->registerUrl, 'clearCacheUrl' => $this->clearCacheUrl, + 'replyToMail' => $this->replyToMail, ] ] ]; diff --git a/app/Form/Mails/ConfirmRegistrationMail.php b/app/Form/Mails/ConfirmRegistrationMail.php index e70253e8..e801d6d4 100644 --- a/app/Form/Mails/ConfirmRegistrationMail.php +++ b/app/Form/Mails/ConfirmRegistrationMail.php @@ -4,6 +4,7 @@ namespace App\Form\Mails; use App\Form\Data\FormConfigData; use App\Form\Editor\FormConditionResolver; +use App\Form\FormSettings; use App\Form\Models\Participant; use App\Lib\Editor\Condition; use Illuminate\Bus\Queueable; @@ -24,6 +25,8 @@ class ConfirmRegistrationMail extends Mailable /** @var array */ public array $bottomText; + public FormSettings $formSettings; + /** * Create a new message instance. * @@ -32,6 +35,7 @@ class ConfirmRegistrationMail extends Mailable public function __construct(public Participant $participant) { $conditionResolver = app(FormConditionResolver::class)->forParticipant($participant); + $this->formSettings = app(FormSettings::class); $this->fullname = $participant->getFields()->getFullname(); $this->config = $participant->getConfig(); $this->topText = $conditionResolver->makeBlocks($participant->form->mail_top); @@ -45,9 +49,15 @@ class ConfirmRegistrationMail extends Mailable */ public function envelope() { - return new Envelope( + $envelope = new Envelope( subject: 'Deine Anmeldung zu ' . $this->participant->form->name, ); + + if ($this->formSettings->replyToMail !== null) { + $envelope->replyTo($this->formSettings->replyToMail); + } + + return $envelope; } /** diff --git a/app/Prevention/Actions/SettingStoreAction.php b/app/Prevention/Actions/SettingStoreAction.php index c04cd68d..5cc80a86 100644 --- a/app/Prevention/Actions/SettingStoreAction.php +++ b/app/Prevention/Actions/SettingStoreAction.php @@ -24,6 +24,7 @@ class SettingStoreAction 'weeks' => 'required|numeric|gte:0', 'freshRememberInterval' => 'required|numeric|gte:0', 'active' => 'boolean', + 'replyToMail' => 'nullable|string|email', ]; } @@ -33,6 +34,7 @@ class SettingStoreAction $settings->formmail = EditorData::from($request->formmail); $settings->yearlymail = EditorData::from($request->yearlymail); $settings->weeks = $request->weeks; + $settings->replyToMail = $request->replyToMail; $settings->freshRememberInterval = $request->freshRememberInterval; $settings->active = $request->active; $settings->yearlyMemberFilter = FilterScope::from($request->yearlyMemberFilter); diff --git a/app/Prevention/Mails/YearlyMail.php b/app/Prevention/Mails/YearlyMail.php index 94b8f84f..7329bb0b 100644 --- a/app/Prevention/Mails/YearlyMail.php +++ b/app/Prevention/Mails/YearlyMail.php @@ -6,6 +6,7 @@ use App\Invoice\InvoiceSettings; use App\Lib\Editor\EditorData; use App\Prevention\Contracts\Preventable; use App\Prevention\Data\PreventionData; +use App\Prevention\PreventionSettings; use Illuminate\Bus\Queueable; use Illuminate\Mail\Attachment; use Illuminate\Mail\Mailable; @@ -19,6 +20,7 @@ class YearlyMail extends Mailable use Queueable, SerializesModels; public InvoiceSettings $settings; + public PreventionSettings $preventionSettings; /** * Create a new message instance. @@ -27,6 +29,7 @@ class YearlyMail extends Mailable public function __construct(public Preventable $preventable, public EditorData $bodyText, public Collection $preventions) { $this->settings = app(InvoiceSettings::class); + $this->preventionSettings = app(PreventionSettings::class); $this->bodyText = $this->bodyText ->replaceWithList('wanted', $preventions->map(fn($prevention) => $prevention->text())->toArray()); } @@ -38,9 +41,15 @@ class YearlyMail extends Mailable */ public function envelope() { - return (new Envelope( + $envelope = (new Envelope( subject: $this->preventable->preventableSubject(), ))->to($this->preventable->getMailRecipient()->email, $this->preventable->getMailRecipient()->name); + + if ($this->preventionSettings->replyToMail !== null) { + $envelope->replyTo($this->preventionSettings->replyToMail); + } + + return $envelope; } /** diff --git a/app/Prevention/PreventionSettings.php b/app/Prevention/PreventionSettings.php index dd2b961f..59eb5875 100644 --- a/app/Prevention/PreventionSettings.php +++ b/app/Prevention/PreventionSettings.php @@ -15,6 +15,7 @@ class PreventionSettings extends LocalSettings public int $freshRememberInterval; public bool $active; public FilterScope $yearlyMemberFilter; + public ?string $replyToMail; /** * @var array * @todo Create collection cast to Collection of enums @@ -49,6 +50,7 @@ class PreventionSettings extends LocalSettings ...$this->toArray(), 'weeks' => (string) $this->weeks, 'freshRememberInterval' => (string) $this->freshRememberInterval, + 'replyToMail' => $this->replyToMail, ]; } } diff --git a/database/settings/202511-07_create_replyto_mail_settings.php b/database/settings/202511-07_create_replyto_mail_settings.php new file mode 100644 index 00000000..3824167c --- /dev/null +++ b/database/settings/202511-07_create_replyto_mail_settings.php @@ -0,0 +1,12 @@ +migrator->add('form.replyToMail', ''); + $this->migrator->add('prevention.replyToMail', ''); + } +}; diff --git a/resources/js/views/setting/Form.vue b/resources/js/views/setting/Form.vue index ff8a95af..8bfd2ebc 100644 --- a/resources/js/views/setting/Form.vue +++ b/resources/js/views/setting/Form.vue @@ -11,6 +11,7 @@
+
diff --git a/resources/js/views/setting/Prevention.vue b/resources/js/views/setting/Prevention.vue index 5c047e68..d5b0d736 100644 --- a/resources/js/views/setting/Prevention.vue +++ b/resources/js/views/setting/Prevention.vue @@ -22,6 +22,7 @@ + diff --git a/tests/EndToEnd/Member/PreventionTest.php b/tests/EndToEnd/Member/PreventionTest.php index 897e0286..8a1696f4 100644 --- a/tests/EndToEnd/Member/PreventionTest.php +++ b/tests/EndToEnd/Member/PreventionTest.php @@ -248,6 +248,17 @@ it('notices a few weeks before', function ($date, bool $shouldSend) { [fn() => now()->subYears(5)->addWeeks(2)->subDay(), false], ]); +it('sets reply to mail', function () { + Mail::fake(); + app(PreventionSettings::class)->fill(['replyToMail' => 'admin@example.com'])->save(); + createMember(['has_vk' => false]); + + sleep(2); + YearlyRememberAction::run(); + + Mail::assertSent(YearlyMail::class, fn ($message) => $message->hasReplyTo('admin@example.com')); +}); + it('remembers members yearly', function ($date, $shouldSend) { Mail::fake(); createMember(['efz' => $date, 'ps_at' => now(), 'has_vk' => true]); diff --git a/tests/Feature/Form/FormRegisterActionTest.php b/tests/Feature/Form/FormRegisterActionTest.php index de56eb67..f45106ab 100644 --- a/tests/Feature/Form/FormRegisterActionTest.php +++ b/tests/Feature/Form/FormRegisterActionTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Form; use App\Form\Enums\NamiType; use App\Form\Enums\SpecialType; +use App\Form\FormSettings; use App\Form\Mails\ConfirmRegistrationMail; use App\Form\Models\Form; use App\Group; @@ -308,6 +309,22 @@ it('testItSendsEmailToParticipant', function () { Mail::assertQueued(ConfirmRegistrationMail::class, fn($message) => $message->hasTo('example@test.test', 'Lala GG') && $message->hasSubject('Deine Anmeldung zu Ver2')); }); +it('sets reply to in email', function () { + $this->login()->loginNami()->withoutExceptionHandling(); + app(FormSettings::class)->fill(['replyToMail' => 'reply@example.com'])->save(); + $form = Form::factory()->name('Ver2')->fields([ + $this->textField('vorname')->specialType(SpecialType::FIRSTNAME), + $this->textField('nachname')->specialType(SpecialType::LASTNAME), + $this->textField('email')->specialType(SpecialType::EMAIL), + ]) + ->create(); + + $this->register($form, ['vorname' => 'Lala', 'nachname' => 'GG', 'email' => 'example@test.test']) + ->assertOk(); + + Mail::assertQueued(ConfirmRegistrationMail::class, fn($message) => $message->hasReplyTo('reply@example.com')); +}); + it('testItDoesntSendEmailWhenNoMailFieldGiven', function () { $this->login()->loginNami()->withoutExceptionHandling(); $form = Form::factory()->fields([ diff --git a/tests/Feature/Prevention/SettingTest.php b/tests/Feature/Prevention/SettingTest.php index 8f2704f0..b14a5c30 100644 --- a/tests/Feature/Prevention/SettingTest.php +++ b/tests/Feature/Prevention/SettingTest.php @@ -28,6 +28,7 @@ it('receives settings', function () { 'weeks' => 9, 'freshRememberInterval' => 11, 'active' => true, + 'replyToMail' => 'admin@example.com', 'preventAgainst' => [Prevention::MOREPS->name], 'yearlyMemberFilter' => FilterScope::from([ 'memberships' => [['group_ids' => [33]]], @@ -41,6 +42,7 @@ it('receives settings', function () { ->assertJsonPath('data.weeks', '9') ->assertJsonPath('data.active', true) ->assertJsonPath('data.freshRememberInterval', '11') + ->assertJsonPath('data.replyToMail', 'admin@example.com') ->assertJsonPath('data.yearlyMemberFilter.search', 'searchstring') ->assertJsonPath('data.yearlyMemberFilter.memberships.0.group_ids.0', 33) ->assertJsonPath('data.preventAgainst', ['MOREPS']) @@ -58,6 +60,7 @@ it('testItStoresSettings', function () { 'freshRememberInterval' => 11, 'active' => true, 'preventAgainst' => ['EFZ'], + 'replyToMail' => 'admin@example.com', 'yearlyMemberFilter' => [ 'memberships' => [['group_ids' => 33]], 'search' => 'searchstring', @@ -66,6 +69,7 @@ it('testItStoresSettings', function () { test()->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem'])); test()->assertTrue(app(PreventionSettings::class)->yearlymail->hasAll(['lala dd'])); test()->assertEquals(9, app(PreventionSettings::class)->weeks); + test()->assertEquals('admin@example.com', app(PreventionSettings::class)->replyToMail); test()->assertEquals(11, app(PreventionSettings::class)->freshRememberInterval); test()->assertTrue(app(PreventionSettings::class)->active); test()->assertEquals([['group_ids' => 33]], app(PreventionSettings::class)->yearlyMemberFilter->memberships);