Neues Modul unter Admin ▸ Newsletter (nur users:manage): Kampagnen mit Betreff + leicht formatiertem Text (Purifier-Profil newsletter: fett/ kursiv, Listen, Links) im zentralen Mail-Layout. Empfänger-Filter auf der User-Datenbank: Portal, Legacy-Vertrag (Bestandsschutz), Login innerhalb/außerhalb X Monaten, Sortierung nach Login/Erstellung — mit Live-Zähler und Vorschau im Editor. Versand friert die Empfängerliste als Protokoll ein (newsletter_ campaign_recipients) und stellt pro Empfänger einen Queue-Job ein; der Opt-out wird im Worker erneut geprüft. Jede Mail trägt den signierten One-Click-Abmelde-Link (neuer EmailNotificationType:: Newsletter, bestehende Unsubscribe-Infrastruktur inkl. RFC 8058); die Abmeldung greift sofort für alle künftigen Auswahlen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
1.9 KiB
PHP
71 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\NewsletterCampaignStatus;
|
|
use App\Services\Newsletter\CampaignRecipients;
|
|
use Database\Factories\NewsletterCampaignFactory;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Support\HtmlString;
|
|
use Mews\Purifier\Facades\Purifier;
|
|
|
|
/**
|
|
* Interne Newsletter-Kampagne (Admin ▸ Newsletter): Betreff + leicht
|
|
* formatierter Text an gefilterte Bestands-User. `filters` hält die
|
|
* Empfänger-Auswahl als Snapshot ({@see CampaignRecipients}),
|
|
* die Empfänger-Zeilen bilden das Versand-Protokoll.
|
|
*/
|
|
class NewsletterCampaign extends Model
|
|
{
|
|
/** @use HasFactory<NewsletterCampaignFactory> */
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'subject',
|
|
'body',
|
|
'filters',
|
|
'status',
|
|
'created_by_user_id',
|
|
'recipients_total',
|
|
'sent_count',
|
|
'failed_count',
|
|
'skipped_count',
|
|
'sent_at',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'filters' => 'array',
|
|
'status' => NewsletterCampaignStatus::class,
|
|
'sent_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
public function createdBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by_user_id');
|
|
}
|
|
|
|
public function recipients(): HasMany
|
|
{
|
|
return $this->hasMany(NewsletterCampaignRecipient::class);
|
|
}
|
|
|
|
public function isDraft(): bool
|
|
{
|
|
return $this->status === NewsletterCampaignStatus::Draft;
|
|
}
|
|
|
|
/**
|
|
* Versandfertiger, gesäuberter Inhalt: nur leichtes HTML laut
|
|
* Purifier-Profil `newsletter` (fett/kursiv, Listen, Links).
|
|
*/
|
|
public function sanitizedBody(): HtmlString
|
|
{
|
|
return new HtmlString((string) Purifier::clean($this->body ?? '', 'newsletter'));
|
|
}
|
|
}
|