Guten Tag{{ $legalRequest->requesterName() ? ' '.$legalRequest->requesterName() : '' }},
+ ++ wir bestätigen den Eingang Ihrer Meldung. Sie ist bei uns am + {{ $legalRequest->created_at->format('d.m.Y') }} + um {{ $legalRequest->created_at->format('H:i') }} Uhr eingegangen. +
+ + + ++ Wir prüfen Ihre Meldung sorgfältig und zeitnah. Anschließend teilen wir Ihnen + unsere Entscheidung mit einer Begründung mit. Bitte geben Sie bei Rückfragen + die Vorgangsnummer an. +
+ ++ Diese Bestätigung wurde automatisch erstellt und dokumentiert Datum und + Uhrzeit des Eingangs Ihrer Meldung. Die Meldung rechtswidriger Inhalte und + die Ausübung gesetzlicher Rechte sind kostenlos. +
+Eine neue Meldung ist eingegangen und wartet in der Queue „Recht & Compliance“.
+ + + +Anliegen:
+{{ $legalRequest->message }}
+ ++ DSA-Meldungen bitte zeitnah bearbeiten — die meldende Person hat eine + Eingangsbestätigung mit dieser Vorgangsnummer erhalten und erwartet eine + begründete Entscheidung. +
++ @if ($isIllegalContent) + Nutzen Sie dieses Formular, wenn Sie eine veröffentlichte Pressemitteilung für + rechtswidrig halten oder Ihre Rechte verletzt sehen. Die Meldung ist kostenlos; + Sie erhalten unverzüglich eine Eingangsbestätigung mit Vorgangsnummer und + anschließend unsere begründete Entscheidung. + @else + Nutzen Sie dieses Formular, wenn Ihre persönlichen Kontaktdaten (z. B. Name, + Telefonnummer oder E-Mail-Adresse) in einer Pressemitteilung stehen. Die Prüfung + erfolgt nach den Vorgaben der DSGVO und ist für Sie kostenlos. + @endif +
++ Alle Wege im Überblick: Inhalte melden oder Änderungen beantragen +
+ + @if (session('report-status')) ++ Ihre Angaben werden ausschließlich zur Bearbeitung Ihrer Meldung verarbeitet — + Details in der Datenschutzerklärung. + Meldungen und die Ausübung gesetzlicher Rechte sind kostenlos. +
+- Meldung: Öffnen Sie die betroffene Pressemitteilung und nutzen Sie dort - den Link „Pressemitteilung melden" — die Mitteilung wird dabei automatisch dem - Vorgang zugeordnet. Alternativ senden Sie Ihre Meldung mit der URL der betroffenen - Pressemitteilung an - {{ $reportEmail }}. + Meldung: Nutzen Sie unser + Meldeformular für + rechtswidrige Inhalte — Sie erhalten unverzüglich eine Eingangsbestätigung mit + Vorgangsnummer. Alternativ öffnen Sie die betroffene Pressemitteilung und nutzen dort + den Link „Inhalt melden" (die Mitteilung wird dabei automatisch dem Vorgang + zugeordnet), oder Sie senden Ihre Meldung mit der URL der betroffenen Pressemitteilung + an {{ $reportEmail }}.
Damit wir Ihre Meldung prüfen können, benötigen wir:
- Antrag: Öffnen Sie die betroffene Pressemitteilung und nutzen Sie dort - den Link „Datenschutz & Persönlichkeitsrecht" — oder senden Sie Ihren - Antrag an {{ $reportEmail }}. + Antrag: Nutzen Sie unser + Antragsformular zur + Entfernung eigener Kontaktdaten. Alternativ öffnen Sie die betroffene + Pressemitteilung und nutzen dort den Link „Eigene Daten entfernen" — oder Sie + senden Ihren Antrag an {{ $reportEmail }}.
Wir prüfen Ihren Antrag nach den Vorgaben der Datenschutz-Grundverordnung. Die Prüfung und diff --git a/routes/web.php b/routes/web.php index a0f9fd54..7b4b91a1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use App\Http\Controllers\Web\NewsroomDetailController; use App\Http\Controllers\Web\NewsroomsController; use App\Http\Controllers\Web\PreiseController; use App\Http\Controllers\Web\ReleaseDetailController; +use App\Http\Controllers\Web\ReportFormController; use App\Http\Controllers\Web\SitemapController; use App\Http\Controllers\Web\SucheController; use App\Http\Controllers\Web\TerminationController; @@ -232,6 +233,16 @@ Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(func // drei Domains ausgeliefert. Route::view('richtlinien', 'web.richtlinien')->name('richtlinien'); Route::view('melden', 'web.melden')->name('melden'); + + // T10: Eigenständige Meldeformulare mit URL-Feld (Meldeverfahren Weg 1/2) + // — Vorgangsnummer + Eingangsbestätigung per E-Mail (DSA Art. 16). + Route::get('melden/{way}', [ReportFormController::class, 'show']) + ->whereIn('way', ReportFormController::WAYS) + ->name('melden.form'); + Route::post('melden/{way}', [ReportFormController::class, 'store']) + ->middleware('throttle:10,1') + ->whereIn('way', ReportFormController::WAYS) + ->name('melden.form.store'); }); // --------------------------------------------------------------------------- diff --git a/tests/Feature/LegalRequestTest.php b/tests/Feature/LegalRequestTest.php index 05eac3bb..4b10a730 100644 --- a/tests/Feature/LegalRequestTest.php +++ b/tests/Feature/LegalRequestTest.php @@ -3,10 +3,13 @@ use App\Enums\LegalRequestStatus; use App\Enums\LegalRequestType; use App\Enums\Portal; +use App\Mail\LegalRequestReceived; +use App\Mail\LegalRequestSubmitted; use App\Models\LegalRequest; use App\Models\PressRelease; use App\Models\User; use Database\Seeders\RolesAndPermissionsSeeder; +use Illuminate\Support\Facades\Mail; use Livewire\Volt\Volt; use Tests\TestCase; @@ -51,13 +54,15 @@ test('the form 404s for a non-published release', function () { test('a dsgvo request is stored in the queue', function () { /** @var TestCase $this */ + Mail::fake(); $release = publishedRelease(); $this->post(route('legal-request.store', ['slug' => $release->slug]), [ 'type' => LegalRequestType::Dsgvo->value, 'requester_email' => 'betroffen@example.test', 'message' => 'Bitte meine personenbezogenen Daten anonymisieren.', - ])->assertRedirect(route('release.detail', ['slug' => $release->slug])); + ])->assertRedirect(route('release.detail', ['slug' => $release->slug])) + ->assertSessionHas('legal-status'); $request = LegalRequest::query()->firstOrFail(); @@ -66,6 +71,11 @@ test('a dsgvo request is stored in the queue', function () { expect($request->press_release_id)->toBe($release->id); expect($request->requester_email)->toBe('betroffen@example.test'); expect($request->portal)->toBe(Portal::Businessportal24->value); + + // T10: Vorgangsnummer im Hinweis + Eingangsbestätigung/Queue-Hinweis. + expect(session('legal-status'))->toContain($request->reference()); + Mail::assertSent(LegalRequestReceived::class, fn ($mail) => $mail->hasTo('betroffen@example.test')); + Mail::assertSent(LegalRequestSubmitted::class); }); test('a personal rights request requires an email', function () { @@ -82,6 +92,7 @@ test('a personal rights request requires an email', function () { test('a report can be submitted without an email and feeds the same queue', function () { /** @var TestCase $this */ + Mail::fake(); $release = publishedRelease(); $this->post(route('legal-request.store', ['slug' => $release->slug]), [ @@ -93,6 +104,10 @@ test('a report can be submitted without an email and feeds the same queue', func expect($request->type)->toBe(LegalRequestType::Report); expect($request->requester_email)->toBeNull(); + + // Ohne E-Mail keine Eingangsbestätigung — der interne Hinweis geht trotzdem raus. + Mail::assertNotSent(LegalRequestReceived::class); + Mail::assertSent(LegalRequestSubmitted::class); }); test('a filled honeypot field blocks bots without creating a request', function (string $field) { diff --git a/tests/Feature/Web/ReportFormTest.php b/tests/Feature/Web/ReportFormTest.php new file mode 100644 index 00000000..cca7bfd7 --- /dev/null +++ b/tests/Feature/Web/ReportFormTest.php @@ -0,0 +1,129 @@ +get("https://presseecho.test/de/melden/{$way}") + ->assertSuccessful() + ->assertSeeText($heading) + ->assertSee('name="reported_url"', false); + + $this->get("https://pressekonto.test/de/melden/{$way}")->assertSuccessful(); +})->with([ + 'rechtswidrig' => ['rechtswidrig', 'Rechtswidrige Inhalte melden'], + 'kontaktdaten' => ['kontaktdaten', 'Eigene Kontaktdaten entfernen lassen'], +]); + +test('an illegal content report creates a request and confirms receipt with a reference', function () { + /** @var TestCase $this */ + Mail::fake(); + + $response = $this->from('https://businessportal24.test/de/melden/rechtswidrig') + ->post('https://businessportal24.test/de/melden/rechtswidrig', [ + 'reported_url' => 'https://businessportal24.com/de/press-release/irgendein-artikel', + 'requester_name' => 'Erika Musterfrau', + 'requester_email' => 'erika@example.com', + 'message' => 'Diese Mitteilung enthält nachweislich falsche Tatsachenbehauptungen über mich.', + 'truth_affirmed' => '1', + ]); + + $response->assertRedirect('https://businessportal24.test/de/melden/rechtswidrig') + ->assertSessionHas('report-status'); + + $request = LegalRequest::query()->sole(); + expect($request->type)->toBe(LegalRequestType::Report); + expect($request->requester_email)->toBe('erika@example.com'); + expect($request->requesterName())->toBe('Erika Musterfrau'); + expect($request->reportedUrl())->toBe('https://businessportal24.com/de/press-release/irgendein-artikel'); + expect($request->payload['truth_affirmed'])->toBeTrue(); + expect($request->portal)->toBe('businessportal24'); + expect(session('report-status'))->toContain($request->reference()); + + // DSA Art. 16 Abs. 4: unverzügliche Eingangsbestätigung + interner Hinweis. + Mail::assertSent(LegalRequestReceived::class, fn ($mail) => $mail->hasTo('erika@example.com')); + Mail::assertSent(LegalRequestSubmitted::class); +}); + +test('a reported url matching a published release is linked to it', function () { + /** @var TestCase $this */ + Mail::fake(); + + $release = PressRelease::factory() + ->published() + ->forPortal(Portal::Businessportal24) + ->create(['published_at' => now()->subDay()]); + + $this->post('https://businessportal24.test/de/melden/rechtswidrig', [ + 'reported_url' => 'https://businessportal24.com/de/press-release/'.$release->slug, + 'requester_name' => 'Erika Musterfrau', + 'requester_email' => 'erika@example.com', + 'message' => 'Diese Mitteilung verletzt meine Markenrechte an mehreren Stellen.', + 'truth_affirmed' => '1', + ]); + + expect(LegalRequest::query()->sole()->press_release_id)->toBe($release->id); +}); + +test('the illegal content report requires the truthfulness affirmation and a valid url', function () { + /** @var TestCase $this */ + Mail::fake(); + + $this->from('https://businessportal24.test/de/melden/rechtswidrig') + ->post('https://businessportal24.test/de/melden/rechtswidrig', [ + 'reported_url' => 'keine-url', + 'requester_name' => 'Erika Musterfrau', + 'requester_email' => 'erika@example.com', + 'message' => 'Diese Mitteilung enthält falsche Tatsachenbehauptungen über mich.', + ]) + ->assertSessionHasErrors(['reported_url', 'truth_affirmed']); + + expect(LegalRequest::query()->count())->toBe(0); + Mail::assertNothingSent(); +}); + +test('the contact data removal form creates a dsgvo request without affirmation checkbox', function () { + /** @var TestCase $this */ + Mail::fake(); + + $this->post('https://presseecho.test/de/melden/kontaktdaten', [ + 'reported_url' => 'https://presseecho.de/de/press-release/alter-artikel', + 'requester_name' => 'Max Mustermann', + 'requester_email' => 'max@example.com', + 'message' => 'Mein Name und meine Telefonnummer stehen im Pressekontakt dieser Mitteilung.', + ])->assertSessionHas('report-status'); + + $request = LegalRequest::query()->sole(); + expect($request->type)->toBe(LegalRequestType::Dsgvo); + expect($request->portal)->toBe('presseecho'); + Mail::assertSent(LegalRequestReceived::class); +}); + +test('a filled honeypot is silently discarded', function () { + /** @var TestCase $this */ + Mail::fake(); + + $this->post('https://businessportal24.test/de/melden/rechtswidrig', [ + 'reported_url' => 'https://businessportal24.com/de/press-release/spam', + 'requester_name' => 'Bot', + 'requester_email' => 'bot@example.com', + 'message' => 'Automatisch generierter Inhalt mit ausreichend vielen Zeichen.', + 'truth_affirmed' => '1', + 'website' => 'https://spam.example.com', + ])->assertSessionHas('report-status'); + + expect(LegalRequest::query()->count())->toBe(0); + Mail::assertNothingSent(); +}); + +test('an unknown form way responds 404', function () { + /** @var TestCase $this */ + $this->get('https://businessportal24.test/de/melden/sonstiges')->assertNotFound(); +});