Add filter for mail top and mail bottom

This commit is contained in:
philipp lang 2024-04-20 00:04:20 +02:00
parent 21dfc4f0b2
commit 1929c8c216
17 changed files with 415 additions and 17 deletions

View File

@ -0,0 +1,51 @@
<?php
namespace App\Form\Editor;
use App\Form\Models\Participant;
use App\Lib\Editor\ConditionResolver;
class FormConditionResolver extends ConditionResolver
{
private Participant $participant;
public function forParticipant(Participant $participant): self
{
$this->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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<string, mixed> */
public array $topText;
/** @var array<string, mixed> */
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);
}
/**

View File

@ -0,0 +1,7 @@
<?php
namespace App\Form\Matchers;
class BooleanMatcher extends SingleValueMatcher
{
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Form\Matchers;
abstract class Matcher
{
public mixed $value;
public function setValue(mixed $value): self
{
$this->value = $value;
return $this;
}
abstract public function matches(string $comparator, mixed $value): bool;
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Form\Matchers;
class SingleValueMatcher extends Matcher
{
public function matches(string $comparator, mixed $value): bool
{
if ($comparator === 'isEqual' && $value === $this->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;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Lib\Editor;
abstract class ConditionResolver
{
/**
* @param array<string, mixed> $block
*/
abstract public function filterBlock(array $block): bool;
/**
* @param array<string, mixed> $content
* @return array<string, mixed>
*/
public function make(array $content): array
{
return array_filter($content['blocks'], fn ($block) => $this->filterBlock($block));
}
}

View File

@ -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');
}
/**

28
app/View/Mail/Editor.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Mail;
use Illuminate\View\Component;
class Editor extends Component
{
/**
* Create a new component instance.
*
* @param array<int, mixed> $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');
}
}

View File

@ -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<Form>
* @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()]);
}
}

View File

@ -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')
<x-mail::panel :type="$block['data']['type']">{!! data_get($block, 'data.message') !!}</x-mail::panel>
@endif
@endforeach

View File

@ -1,9 +1,8 @@
@component('mail::message')
<x-mail::message>
# Hallo {{$fullname}},
{{ $participant->form->mail_top }}
# Deine Daten
<x-mail-view::editor :content="$topText"></x-mail-view::editor>
@foreach($config->sections as $section)
## {{$section->name}}
@ -12,6 +11,6 @@
@endforeach
@endforeach
{{ $participant->form->mail_bottom }}
<x-mail-view::editor :content="$bottomText"></x-mail-view::editor>
@endcomponent
</x-mail::message>

View File

@ -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::');
}
}
}

View File

@ -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<string, mixed>
*/
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
]
]
],
];
}