From 1929c8c2168bc95010a8c29c5ea486e8ac1798f0 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sat, 20 Apr 2024 00:04:20 +0200 Subject: [PATCH] Add filter for mail top and mail bottom --- app/Form/Editor/FormConditionResolver.php | 51 ++++++ app/Form/Fields/CheckboxField.php | 7 + app/Form/Fields/DropdownField.php | 7 + app/Form/Fields/Field.php | 5 + app/Form/Fields/RadioField.php | 7 + app/Form/Mails/ConfirmRegistrationMail.php | 9 +- app/Form/Matchers/BooleanMatcher.php | 7 + app/Form/Matchers/Matcher.php | 18 ++ app/Form/Matchers/SingleValueMatcher.php | 28 +++ app/Lib/Editor/ConditionResolver.php | 21 +++ app/Providers/AppServiceProvider.php | 3 + app/View/Mail/Editor.php | 28 +++ .../factories/Form/Models/FormFactory.php | 21 ++- .../views/components/mail/editor.blade.php | 31 ++++ .../mail/form/confirm-registration.blade.php | 11 +- tests/Feature/Form/FormRegisterMailTest.php | 163 ++++++++++++++++++ .../RequestFactories/EditorRequestFactory.php | 15 +- 17 files changed, 415 insertions(+), 17 deletions(-) create mode 100644 app/Form/Editor/FormConditionResolver.php create mode 100644 app/Form/Matchers/BooleanMatcher.php create mode 100644 app/Form/Matchers/Matcher.php create mode 100644 app/Form/Matchers/SingleValueMatcher.php create mode 100644 app/Lib/Editor/ConditionResolver.php create mode 100644 app/View/Mail/Editor.php create mode 100644 resources/views/components/mail/editor.blade.php diff --git a/app/Form/Editor/FormConditionResolver.php b/app/Form/Editor/FormConditionResolver.php new file mode 100644 index 00000000..76ce94eb --- /dev/null +++ b/app/Form/Editor/FormConditionResolver.php @@ -0,0 +1,51 @@ +participant = $participant; + + return $this; + } + + /** + * @inheritdoc + */ + public function filterBlock(array $block): bool + { + $mode = data_get($block, 'tunes.condition.mode', 'any'); + $ifs = data_get($block, 'tunes.condition.ifs', []); + + if (count($ifs) === 0) { + return true; + } + + foreach ($ifs as $if) { + $field = $this->participant->getFields()->findByKey($if['field']); + $matches = $field->matches($if['comparator'], $if['value']); + if ($matches && $mode === 'any') { + return true; + } + if (!$matches && $mode === 'all') { + return false; + } + } + + if ($mode === 'any') { + return false; + } + + if ($mode === 'all') { + return true; + } + } +} diff --git a/app/Form/Fields/CheckboxField.php b/app/Form/Fields/CheckboxField.php index 511c039a..c649b10b 100644 --- a/app/Form/Fields/CheckboxField.php +++ b/app/Form/Fields/CheckboxField.php @@ -2,6 +2,8 @@ namespace App\Form\Fields; +use App\Form\Matchers\BooleanMatcher; +use App\Form\Matchers\Matcher; use App\Form\Models\Form; use App\Form\Models\Participant; use App\Form\Presenters\BooleanPresenter; @@ -79,4 +81,9 @@ class CheckboxField extends Field { return app(BooleanPresenter::class); } + + public function getMatcher(): Matcher + { + return app(BooleanMatcher::class); + } } diff --git a/app/Form/Fields/DropdownField.php b/app/Form/Fields/DropdownField.php index 42d13abc..e903ac4b 100644 --- a/app/Form/Fields/DropdownField.php +++ b/app/Form/Fields/DropdownField.php @@ -2,6 +2,8 @@ namespace App\Form\Fields; +use App\Form\Matchers\Matcher; +use App\Form\Matchers\SingleValueMatcher; use App\Form\Models\Form; use App\Form\Models\Participant; use Faker\Generator; @@ -80,4 +82,9 @@ class DropdownField extends Field public function afterRegistration(Form $form, Participant $participant, array $input): void { } + + public function getMatcher(): Matcher + { + return app(SingleValueMatcher::class); + } } diff --git a/app/Form/Fields/Field.php b/app/Form/Fields/Field.php index 209e90a4..3da10efe 100644 --- a/app/Form/Fields/Field.php +++ b/app/Form/Fields/Field.php @@ -165,4 +165,9 @@ abstract class Field extends Data { return $this->key . '_display'; } + + public function matches(string $comparator, mixed $value): bool + { + return $this->getMatcher()->setValue($this->value)->matches($comparator, $value); + } } diff --git a/app/Form/Fields/RadioField.php b/app/Form/Fields/RadioField.php index ba7748e2..07705278 100644 --- a/app/Form/Fields/RadioField.php +++ b/app/Form/Fields/RadioField.php @@ -2,6 +2,8 @@ namespace App\Form\Fields; +use App\Form\Matchers\Matcher; +use App\Form\Matchers\SingleValueMatcher; use App\Form\Models\Form; use App\Form\Models\Participant; use Faker\Generator; @@ -80,4 +82,9 @@ class RadioField extends Field public function afterRegistration(Form $form, Participant $participant, array $input): void { } + + public function getMatcher(): Matcher + { + return app(SingleValueMatcher::class); + } } diff --git a/app/Form/Mails/ConfirmRegistrationMail.php b/app/Form/Mails/ConfirmRegistrationMail.php index 89ab5c48..208d3169 100644 --- a/app/Form/Mails/ConfirmRegistrationMail.php +++ b/app/Form/Mails/ConfirmRegistrationMail.php @@ -3,6 +3,7 @@ namespace App\Form\Mails; use App\Form\Data\FormConfigData; +use App\Form\Editor\FormConditionResolver; use App\Form\Models\Participant; use Illuminate\Bus\Queueable; use Illuminate\Mail\Attachment; @@ -10,7 +11,6 @@ use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Storage; class ConfirmRegistrationMail extends Mailable { @@ -18,6 +18,10 @@ class ConfirmRegistrationMail extends Mailable public string $fullname; public FormConfigData $config; + /** @var array */ + public array $topText; + /** @var array */ + public array $bottomText; /** * Create a new message instance. @@ -26,8 +30,11 @@ class ConfirmRegistrationMail extends Mailable */ public function __construct(public Participant $participant) { + $conditionResolver = app(FormConditionResolver::class)->forParticipant($participant); $this->fullname = $participant->getFields()->getFullname(); $this->config = $participant->getConfig(); + $this->topText = $conditionResolver->make($participant->form->mail_top); + $this->bottomText = $conditionResolver->make($participant->form->mail_bottom); } /** diff --git a/app/Form/Matchers/BooleanMatcher.php b/app/Form/Matchers/BooleanMatcher.php new file mode 100644 index 00000000..1182f75d --- /dev/null +++ b/app/Form/Matchers/BooleanMatcher.php @@ -0,0 +1,7 @@ +value = $value; + + return $this; + } + + abstract public function matches(string $comparator, mixed $value): bool; +} diff --git a/app/Form/Matchers/SingleValueMatcher.php b/app/Form/Matchers/SingleValueMatcher.php new file mode 100644 index 00000000..80e98ad7 --- /dev/null +++ b/app/Form/Matchers/SingleValueMatcher.php @@ -0,0 +1,28 @@ +value) { + return true; + } + + if ($comparator === 'isNotEqual' && $value !== $this->value) { + return true; + } + + if ($comparator === 'isIn' && in_array($this->value, $value)) { + return true; + } + + if ($comparator === 'isNotIn' && !in_array($this->value, $value)) { + return true; + } + + return false; + } +} diff --git a/app/Lib/Editor/ConditionResolver.php b/app/Lib/Editor/ConditionResolver.php new file mode 100644 index 00000000..713c92b3 --- /dev/null +++ b/app/Lib/Editor/ConditionResolver.php @@ -0,0 +1,21 @@ + $block + */ + abstract public function filterBlock(array $block): bool; + + /** + * @param array $content + * @return array + */ + public function make(array $content): array + { + return array_filter($content['blocks'], fn ($block) => $this->filterBlock($block)); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 53dc5dc6..41b8353d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -7,6 +7,7 @@ use App\Mailgateway\Types\LocalType; use App\Mailgateway\Types\MailmanType; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; use Laravel\Telescope\Telescope; @@ -36,6 +37,8 @@ class AppServiceProvider extends ServiceProvider ])); app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class)); + + Blade::componentNamespace('App\\View\\Mail', 'mail-view'); } /** diff --git a/app/View/Mail/Editor.php b/app/View/Mail/Editor.php new file mode 100644 index 00000000..987a300c --- /dev/null +++ b/app/View/Mail/Editor.php @@ -0,0 +1,28 @@ + $content + * @return void + */ + public function __construct(public array $content) + { + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\Contracts\View\View|\Closure|string + */ + public function render() + { + return view('components.mail.editor'); + } +} diff --git a/database/factories/Form/Models/FormFactory.php b/database/factories/Form/Models/FormFactory.php index 4b13401b..5fe993e5 100644 --- a/database/factories/Form/Models/FormFactory.php +++ b/database/factories/Form/Models/FormFactory.php @@ -7,16 +7,15 @@ use Database\Factories\Traits\FakesMedia; use Illuminate\Database\Eloquent\Factories\Factory; use Tests\Feature\Form\FormtemplateFieldRequest; use Tests\Feature\Form\FormtemplateSectionRequest; +use Tests\RequestFactories\EditorRequestFactory; /** * @extends Factory
* @method self name(string $name) * @method self from(string $from) * @method self to(string $to) - * @method self mailTop(string $content) - * @method self mailBottom(string $content) * @method self excerpt(string $excerpt) - * @method self description(string $description) + * @method self description(EditorRequestFactory $description) * @method self registrationFrom(string|null $date) * @method self registrationUntil(string|null $date) */ @@ -40,15 +39,15 @@ class FormFactory extends Factory { return [ 'name' => $this->faker->words(4, true), - 'description' => $this->faker->text(), + 'description' => EditorRequestFactory::new()->create(), 'excerpt' => $this->faker->words(10, true), 'config' => ['sections' => []], 'from' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'), 'to' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'), 'registration_from' => $this->faker->dateTimeBetween('-2 weeks', 'now')->format('Y-m-d H:i:s'), 'registration_until' => $this->faker->dateTimeBetween('now', '+2 weeks')->format('Y-m-d H:i:s'), - 'mail_top' => $this->faker->text(), - 'mail_bottom' => $this->faker->text(), + 'mail_top' => EditorRequestFactory::new()->create(), + 'mail_bottom' => EditorRequestFactory::new()->create(), ]; } @@ -75,4 +74,14 @@ class FormFactory extends Factory { return $this->state([str($method)->snake()->toString() => $parameters[0]]); } + + public function mailTop(EditorRequestFactory $factory): self + { + return $this->state(['mail_top' => $factory->create()]); + } + + public function mailBottom(EditorRequestFactory $factory): self + { + return $this->state(['mail_bottom' => $factory->create()]); + } } diff --git a/resources/views/components/mail/editor.blade.php b/resources/views/components/mail/editor.blade.php new file mode 100644 index 00000000..2a75bf2b --- /dev/null +++ b/resources/views/components/mail/editor.blade.php @@ -0,0 +1,31 @@ +@foreach ($content as $block) + +@if ($block['type'] === 'paragraph') +{!! $block['data']['text'] !!} +@endif + +@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 2) +## {!! data_get($block, 'data.text') !!} +@endif + +@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 3) +### {!! data_get($block, 'data.text') !!} +@endif + +@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 4) +#### {!! data_get($block, 'data.text') !!} +@endif + +@if ($block['type'] === 'list' && data_get($block, 'data.style', 'unordered') === 'unordered') +{!! collect(data_get($block, 'data.items', []))->map(fn ($item) => '* '.$item['content'])->implode("\n") !!} +@endif + +@if ($block['type'] === 'list' && data_get($block, 'data.style', 'unordered') === 'ordered') +{!! collect(data_get($block, 'data.items', []))->map(fn ($item) => '1. '.$item['content'])->implode("\n") !!} +@endif + +@if ($block['type'] === 'alert') +{!! data_get($block, 'data.message') !!} +@endif + +@endforeach diff --git a/resources/views/mail/form/confirm-registration.blade.php b/resources/views/mail/form/confirm-registration.blade.php index 21106190..e8197c38 100644 --- a/resources/views/mail/form/confirm-registration.blade.php +++ b/resources/views/mail/form/confirm-registration.blade.php @@ -1,9 +1,8 @@ -@component('mail::message') + + # Hallo {{$fullname}}, -{{ $participant->form->mail_top }} - -# Deine Daten + @foreach($config->sections as $section) ## {{$section->name}} @@ -12,6 +11,6 @@ @endforeach @endforeach -{{ $participant->form->mail_bottom }} + -@endcomponent + diff --git a/tests/Feature/Form/FormRegisterMailTest.php b/tests/Feature/Form/FormRegisterMailTest.php index 3a057037..cdcde559 100644 --- a/tests/Feature/Form/FormRegisterMailTest.php +++ b/tests/Feature/Form/FormRegisterMailTest.php @@ -6,6 +6,7 @@ use App\Form\Enums\SpecialType; use App\Form\Mails\ConfirmRegistrationMail; use App\Form\Models\Form; use App\Form\Models\Participant; +use Generator; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\RequestFactories\EditorRequestFactory; @@ -75,4 +76,166 @@ class FormRegisterMailTest extends FormTestCase $mail->assertHasAttachedData('content1', 'beispiel.pdf', ['mime' => 'application/pdf']); $mail->assertHasAttachedData('content2', 'beispiel2.pdf', ['mime' => 'application/pdf']); } + + public function blockDataProvider(): Generator + { + yield [ + ['mode' => 'all', 'ifs' => []], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'any', 'ifs' => []], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'any', 'ifs' => []], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'any', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'any', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + false, + ]; + + yield [ + ['mode' => 'any', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'], + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'any', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'], + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'B'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'], + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'B'], + false, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'B'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isNotEqual', 'value' => 'A'] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'B'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isIn', 'value' => ['A']] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isNotIn', 'value' => ['B']] + ]], + $this->dropdownField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isNotIn', 'value' => ['B']] + ]], + $this->radioField('fieldkey')->options(['A', 'B']), + ['fieldkey' => 'A'], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => true] + ]], + $this->checkboxField('fieldkey'), + ['fieldkey' => true], + true, + ]; + + yield [ + ['mode' => 'all', 'ifs' => [ + ['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => false] + ]], + $this->checkboxField('fieldkey'), + ['fieldkey' => true], + false, + ]; + } + + /** + * @dataProvider blockDataProvider + */ + public function testItFiltersForBlockConditions(array $conditions, FormtemplateFieldRequest $field, array $participantValues, bool $result): void + { + $this->login()->loginNami()->withoutExceptionHandling(); + + $participant = Participant::factory()->for( + Form::factory() + ->fields([ + $field, + $this->textField('firstname')->specialType(SpecialType::FIRSTNAME), + $this->textField('lastname')->specialType(SpecialType::LASTNAME), + ]) + ->mailTop(EditorRequestFactory::new()->text(10, '::content::', $conditions)) + ) + ->data(['firstname' => 'Max', 'lastname' => 'Muster', ...$participantValues]) + ->create(); + + $mail = new ConfirmRegistrationMail($participant); + if ($result) { + $mail->assertSeeInText('::content::'); + } else { + $mail->assertDontSeeInText('::content::'); + } + } } diff --git a/tests/RequestFactories/EditorRequestFactory.php b/tests/RequestFactories/EditorRequestFactory.php index 230d7799..da9c10bf 100644 --- a/tests/RequestFactories/EditorRequestFactory.php +++ b/tests/RequestFactories/EditorRequestFactory.php @@ -20,21 +20,28 @@ class EditorRequestFactory extends RequestFactory ]; } - public function text(int $id, string $text): self + public function text(int $id, string $text, array $conditions = ['mode' => 'all', 'ifs' => []]): self { - return $this->state($this->paragraphBlock($id, $text)); + return $this->state($this->paragraphBlock($id, $text, $conditions)); } /** * @return array */ - public function paragraphBlock(int $id, string $text): array + public function paragraphBlock(int $id, string $text, array $conditions = ['mode' => 'all', 'ifs' => []]): array { return [ 'time' => 1, 'version' => '1.0', 'blocks' => [ - ['id' => $id, 'type' => 'paragraph', 'data' => ['text' => $text]] + [ + 'id' => $id, + 'type' => 'paragraph', + 'data' => ['text' => $text], + 'tunes' => [ + 'condition' => $conditions + ] + ] ], ]; }