Ablehnungskategorien live aus der DB (rejection_categories)
/richtlinien rendert jetzt alle aktiven Kategorien aus der neuen rejection_categories-Tabelle (stabiler code als Anker, public_label/desc, optionale Beispiele) statt der statischen K1-K6-Liste. Kategorien werden nie geloescht, nur deaktiviert; neue Kategorien erscheinen ohne Textaenderung und ohne neue AGB-Version (AGB par. 7 Abs. 3 verweist nicht abschliessend). Seeder legt K1-K6 idempotent an; Versionszeile der Seite kommt aus legal_documents. Go-Live-Checkliste um beide Seeder ergaenzt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7510e7633e
commit
0bec71ffa5
8 changed files with 213 additions and 18 deletions
59
app/Models/RejectionCategory.php
Normal file
59
app/Models/RejectionCategory.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\RejectionCategoryFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Ablehnungskategorie der Inhaltsprüfung (Richtlinien v0.1, AGB § 7 Abs. 3).
|
||||
*
|
||||
* Der `code` (K1, K2, …) ist stabil und wird in Ablehnungs-Begründungen
|
||||
* referenziert; die öffentliche Richtlinien-Seite rendert alle aktiven
|
||||
* Kategorien als Anker-Abschnitte. Kategorien werden nie gelöscht, nur
|
||||
* deaktiviert (`active = false`).
|
||||
*/
|
||||
class RejectionCategory extends Model
|
||||
{
|
||||
/** @use HasFactory<RejectionCategoryFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'public_label',
|
||||
'public_desc',
|
||||
'examples',
|
||||
'severity',
|
||||
'rubric_ref',
|
||||
'active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<RejectionCategory> $query
|
||||
* @return Builder<RejectionCategory>
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<RejectionCategory> $query
|
||||
* @return Builder<RejectionCategory>
|
||||
*/
|
||||
public function scopeOrdered(Builder $query): Builder
|
||||
{
|
||||
return $query->orderBy('sort_order')->orderBy('code');
|
||||
}
|
||||
}
|
||||
36
database/factories/RejectionCategoryFactory.php
Normal file
36
database/factories/RejectionCategoryFactory.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\RejectionCategory;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<RejectionCategory>
|
||||
*/
|
||||
class RejectionCategoryFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'K'.fake()->unique()->numberBetween(1, 999),
|
||||
'public_label' => fake()->sentence(3),
|
||||
'public_desc' => fake()->sentence(12),
|
||||
'examples' => null,
|
||||
'severity' => 'rot',
|
||||
'rubric_ref' => null,
|
||||
'active' => true,
|
||||
'sort_order' => fake()->numberBetween(0, 100),
|
||||
];
|
||||
}
|
||||
|
||||
public function inactive(): static
|
||||
{
|
||||
return $this->state(['active' => false]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Ablehnungskategorien (Rechtstexte-Architektur E3, Richtlinien v0.1):
|
||||
* Die öffentliche Richtlinien-Seite rendert alle aktiven Kategorien live;
|
||||
* Ablehnungs-Mails referenzieren den stabilen `code` (AGB § 7 Abs. 3).
|
||||
* Kategorien werden nie gelöscht, nur deaktiviert — Ablehnungen der
|
||||
* Vergangenheit müssen referenzierbar bleiben.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('rejection_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('code', 20)->unique(); // z. B. "K1", stabil
|
||||
$table->string('public_label');
|
||||
$table->text('public_desc');
|
||||
$table->text('examples')->nullable(); // optional, öffentlich
|
||||
$table->string('severity', 20)->default('rot'); // rot | gelb_hinweis
|
||||
$table->string('rubric_ref', 80)->nullable(); // interne Rubrik, nie öffentlich
|
||||
$table->boolean('active')->default(true);
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['active', 'sort_order']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('rejection_categories');
|
||||
}
|
||||
};
|
||||
|
|
@ -22,6 +22,7 @@ class DatabaseSeeder extends Seeder
|
|||
PlanSeeder::class,
|
||||
PaymentOptionSeeder::class,
|
||||
LegalDocumentSeeder::class,
|
||||
RejectionCategorySeeder::class,
|
||||
// CategorySeeder::class,
|
||||
]);
|
||||
|
||||
|
|
|
|||
39
database/seeders/RejectionCategorySeeder.php
Normal file
39
database/seeders/RejectionCategorySeeder.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\RejectionCategory;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Start-Kategorien K1–K6 (Richtlinien v0.1, docs/legal). Idempotent über
|
||||
* den stabilen `code`; bestehende Kategorien werden aktualisiert, nie
|
||||
* gelöscht. Neue Kategorien sind neue Zeilen ohne AGB-Änderung.
|
||||
*/
|
||||
class RejectionCategorySeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$categories = [
|
||||
['K1', 'Rechtswidrige Inhalte', 'Inhalte, die gegen geltendes Recht verstoßen — etwa strafbare Inhalte, Verstöße gegen das Wettbewerbs- oder Heilmittelwerberecht, Angebote verbotener Produkte oder Dienstleistungen.'],
|
||||
['K2', 'Explizite oder jugendgefährdende Inhalte', 'Pornografische, gewaltverherrlichende oder anderweitig jugendgefährdende Darstellungen in Text oder Bild.'],
|
||||
['K3', 'Hass und Diskriminierung', 'Inhalte, die Menschen wegen ihrer Herkunft, Religion, Geschlechtsidentität, sexuellen Orientierung, Behinderung oder vergleichbarer Merkmale herabwürdigen oder zu Hass aufrufen.'],
|
||||
['K4', 'Irreführung und Falschbehauptung', 'Nachweislich falsche Tatsachenbehauptungen, erfundene Studien oder Zitate, Täuschung über den Absender sowie gesundheits- oder finanzbezogene Heilsversprechen ohne Grundlage.'],
|
||||
['K5', 'Rechte Dritter', 'Verletzung von Urheber-, Marken- oder Persönlichkeitsrechten — etwa fremde Texte oder Bilder ohne Lizenz, unbefugte Namensnennung, herabsetzende Aussagen über Dritte.'],
|
||||
['K6', 'Kein Pressecharakter / Spam', 'Reine Werbe- oder Verkaufstexte ohne Nachrichtenwert, Keyword-Spam, automatisch generierte Masseninhalte ohne inhaltliche Substanz, Duplikate bereits veröffentlichter Mitteilungen.'],
|
||||
];
|
||||
|
||||
foreach ($categories as $index => [$code, $label, $desc]) {
|
||||
RejectionCategory::query()->updateOrCreate(
|
||||
['code' => $code],
|
||||
[
|
||||
'public_label' => $label,
|
||||
'public_desc' => $desc,
|
||||
'severity' => 'rot',
|
||||
'active' => true,
|
||||
'sort_order' => ($index + 1) * 10,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +125,11 @@ php artisan legacy:grandfather-subscriptions
|
|||
|
||||
# Favicons für alle Domains generieren
|
||||
php artisan domains:generate-favicons
|
||||
|
||||
# Rechtstext-Versionen (E3) + Ablehnungskategorien K1–K6 anlegen — ohne sie
|
||||
# fehlen AGB-Versionsangabe/Protokoll-Referenz und die /richtlinien-Kategorien.
|
||||
php artisan db:seed --class=LegalDocumentSeeder --force
|
||||
php artisan db:seed --class=RejectionCategorySeeder --force
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -4,22 +4,20 @@
|
|||
@section('meta_description', 'Veröffentlichungsrichtlinien für Pressemitteilungen auf presseecho.de und businessportal24.com: Prüfverfahren, Anforderungen und Ablehnungskategorien.')
|
||||
|
||||
@php
|
||||
// Rechtstexte-Architektur E3: Rahmentext v0.1 (03.07.2026). Die
|
||||
// Ablehnungskategorien sind hier zum Launch statisch (K1–K6); das
|
||||
// rejection_categories-System (live aus der DB) ist R3.1-Backlog.
|
||||
// Rechtstexte-Architektur E3: statischer Rahmentext + dynamische
|
||||
// Kategorienliste aus rejection_categories — neue/angepasste Kategorien
|
||||
// erscheinen ohne Textänderung. Version/Stand aus legal_documents.
|
||||
$host = request()->getHost();
|
||||
$isHub = str_contains($host, 'pressekonto');
|
||||
$brandKey = $isHub ? 'pressekonto' : (str_contains($host, 'presseecho') ? 'presseecho' : 'businessportal24');
|
||||
$contactEmail = (string) config('legal.contact_email');
|
||||
|
||||
$rejectionCategories = [
|
||||
['code' => 'K1', 'label' => 'Rechtswidrige Inhalte', 'desc' => 'Inhalte, die gegen geltendes Recht verstoßen — etwa strafbare Inhalte, Verstöße gegen das Wettbewerbs- oder Heilmittelwerberecht, Angebote verbotener Produkte oder Dienstleistungen.'],
|
||||
['code' => 'K2', 'label' => 'Explizite oder jugendgefährdende Inhalte', 'desc' => 'Pornografische, gewaltverherrlichende oder anderweitig jugendgefährdende Darstellungen in Text oder Bild.'],
|
||||
['code' => 'K3', 'label' => 'Hass und Diskriminierung', 'desc' => 'Inhalte, die Menschen wegen ihrer Herkunft, Religion, Geschlechtsidentität, sexuellen Orientierung, Behinderung oder vergleichbarer Merkmale herabwürdigen oder zu Hass aufrufen.'],
|
||||
['code' => 'K4', 'label' => 'Irreführung und Falschbehauptung', 'desc' => 'Nachweislich falsche Tatsachenbehauptungen, erfundene Studien oder Zitate, Täuschung über den Absender sowie gesundheits- oder finanzbezogene Heilsversprechen ohne Grundlage.'],
|
||||
['code' => 'K5', 'label' => 'Rechte Dritter', 'desc' => 'Verletzung von Urheber-, Marken- oder Persönlichkeitsrechten — etwa fremde Texte oder Bilder ohne Lizenz, unbefugte Namensnennung, herabsetzende Aussagen über Dritte.'],
|
||||
['code' => 'K6', 'label' => 'Kein Pressecharakter / Spam', 'desc' => 'Reine Werbe- oder Verkaufstexte ohne Nachrichtenwert, Keyword-Spam, automatisch generierte Masseninhalte ohne inhaltliche Substanz, Duplikate bereits veröffentlichter Mitteilungen.'],
|
||||
];
|
||||
$rejectionCategories = \App\Models\RejectionCategory::query()->active()->ordered()->get();
|
||||
|
||||
$guidelinesDocument = \App\Models\LegalDocument::current(\App\Models\LegalDocument::TYPE_RICHTLINIEN);
|
||||
$guidelinesStand = $guidelinesDocument
|
||||
? 'Stand: '.$guidelinesDocument->effectiveFromFormatted().' · Version '.$guidelinesDocument->version
|
||||
: 'Stand: 3. Juli 2026 · Version 0.1';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
|
|
@ -27,7 +25,7 @@
|
|||
eyebrow="Rechtliches"
|
||||
title="Veröffentlichungsrichtlinien"
|
||||
breadcrumb="Richtlinien"
|
||||
updated="Stand: 3. Juli 2026 · Version 0.1"
|
||||
updated="{{ $guidelinesStand }}"
|
||||
>
|
||||
<div class="pm-body max-w-[760px]">
|
||||
<p>
|
||||
|
|
@ -69,12 +67,15 @@
|
|||
|
||||
<div class="max-w-[760px] mt-4 border border-bg-rule divide-y divide-bg-rule">
|
||||
@foreach ($rejectionCategories as $category)
|
||||
<div id="{{ $category['code'] }}" class="px-5 py-4 scroll-mt-6">
|
||||
<div id="{{ $category->code }}" class="px-5 py-4 scroll-mt-6">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="font-mono text-[12px] font-bold text-ink-3 flex-shrink-0">{{ $category['code'] }}</span>
|
||||
<span class="font-mono text-[12px] font-bold text-ink-3 flex-shrink-0">{{ $category->code }}</span>
|
||||
<div>
|
||||
<h3 class="text-[14.5px] font-semibold text-ink m-0">{{ $category['label'] }}</h3>
|
||||
<p class="text-[13px] leading-[1.6] text-ink-2 mt-1.5 mb-0">{{ $category['desc'] }}</p>
|
||||
<h3 class="text-[14.5px] font-semibold text-ink m-0">{{ $category->public_label }}</h3>
|
||||
<p class="text-[13px] leading-[1.6] text-ink-2 mt-1.5 mb-0">{{ $category->public_desc }}</p>
|
||||
@if ($category->examples)
|
||||
<p class="text-[12px] leading-[1.6] text-ink-3 mt-1.5 mb-0">Beispiele: {{ $category->examples }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\RejectionCategory;
|
||||
use Database\Seeders\RejectionCategorySeeder;
|
||||
use Tests\TestCase;
|
||||
|
||||
dataset('static pages', [
|
||||
|
|
@ -75,12 +77,25 @@ test('the terms render the b2b version with cancellation and archive rules', fun
|
|||
|
||||
test('the guidelines list the rejection categories with anchors', function () {
|
||||
/** @var TestCase $this */
|
||||
// Kategorien kommen live aus rejection_categories (E3) — neue Zeilen
|
||||
// erscheinen ohne Textänderung, deaktivierte verschwinden.
|
||||
$this->seed(RejectionCategorySeeder::class);
|
||||
RejectionCategory::query()->where('code', 'K6')->update(['active' => false]);
|
||||
RejectionCategory::factory()->create([
|
||||
'code' => 'K7',
|
||||
'public_label' => 'Testkategorie aus der Datenbank',
|
||||
'sort_order' => 70,
|
||||
]);
|
||||
|
||||
$this->get('https://presseecho.test/de/richtlinien')
|
||||
->assertSuccessful()
|
||||
->assertSeeText('So funktioniert die Prüfung')
|
||||
->assertSeeText('Kein Pressecharakter / Spam')
|
||||
->assertSeeText('Rechtswidrige Inhalte')
|
||||
->assertSeeText('Testkategorie aus der Datenbank')
|
||||
->assertSee('id="K1"', false)
|
||||
->assertSee('id="K6"', false);
|
||||
->assertSee('id="K7"', false)
|
||||
// Deaktivierte Kategorien werden nicht mehr gerendert.
|
||||
->assertDontSeeText('Kein Pressecharakter / Spam');
|
||||
});
|
||||
|
||||
test('the reporting page separates free legal paths from the paid takedown service', function () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue