Appearance
Aktivitätsprotokoll und Undo
Das PIM protokolliert Änderungen mit spatie/laravel-activitylog im Log pim. Darauf bauen zwei Undo-Wege auf. Die Benutzersicht steht in PIM → Aktivitäten.
Aufzeichnen: TracksActivity
admin/app/Concerns/TracksActivity.php ist ein Trait für Models. Er hängt sich selbst an die Model-Events created, updated und deleted. Spaties eigenes LogsActivity wird bewusst nicht verwendet, weil es bei $guarded = [] und übersetzbaren Feldern Werte verliert.
| Event | properties |
|---|---|
created | { attributes: alle Attribute } |
updated | { attributes: geänderte Felder, old: deren alte Werte }. Kein Eintrag, wenn sich nur ignorierte Felder geändert haben |
deleted | { old: alle Attribute } |
Nie protokolliert werden created_at, updated_at, alle $hidden-Attribute des Models (Passwörter, 2FA-Geheimnisse) und was das Model in $activityIgnore nennt.
Jeder Eintrag hat subject (das Model), causer (angemeldeter Benutzer, sonst „System“) und event.
Neues Model protokollieren:
php
use App\Concerns\TracksActivity;
final class Example extends Model
{
use TracksActivity;
/** @var list<string> Technical columns that only add noise. */
protected array $activityIgnore = ['last_synced_at'];
}Damit es in der Liste lesbar erscheint, braucht die Klasse eine Bezeichnung (ActivitySubject::titleOf und die Labels in lang/de/pim.php).
Verknüpfungen: RelationActivity
Pivot-Änderungen lösen keine Model-Events aus. admin/app/Support/RelationActivity.php schreibt dafür eigene Einträge:
php
RelationActivity::record($group, RelationActivity::ATTACHED, 'spareParts', [$articleId => ['position' => 3]]);- Events
attachedunddetached,properties={ relation, pivots: { relatedId: pivotWerte } }. reverse()kehrt sie um:attached→detach,detached→syncWithoutDetachingmit den gespeicherten Pivot-Werten. Nur fürBelongsToMany.- Derzeit nur in
app/Livewire/ArticleGroupArticleTables.phpverwendet (Hauptartikel und Ersatzteile in Projekten). Andere Pivot-Änderungen erscheinen nicht als eigene Einträge.
Undo aus der Aktivitätenliste: ActivityUndo
admin/app/Support/ActivityUndo.php, aufgerufen aus Filament/Resources/Activities/Tables/ActivitiesTable.php. Die Aktion braucht das Recht Restore:Activity.
blocker(Activity, ?User): ?string liefert den Grund, warum es nicht geht (Lang-Keys pim.activity.undo_blocked.*), sonst null:
| Event | Gesperrt, wenn |
|---|---|
| alle | properties.reverted_at gesetzt (already); Subject-Klasse unbekannt (unsupported) |
updated | Datensatz weg (gone); es gibt einen neueren updated/reverted/restored-Eintrag (newer); keine wiederherstellbaren Spalten (nothing); update nicht erlaubt (forbidden) |
deleted | Datensatz existiert und ist nicht im Papierkorb (exists); keine alten Werte (nothing); create auf der Klasse nicht erlaubt |
created | Datensatz weg (gone); seitdem geändert (newer); delete nicht erlaubt |
attached/detached | Datensatz weg; update nicht erlaubt |
undo() läuft in einer Transaktion:
updated: alte Werte, gefiltert auf echte Tabellenspalten, perforceFill()->saveQuietly()zurück. Neuer Eintragrevertedmitundid_activity_idundrestored.saveQuietlyverhindert einen zusätzlichenupdated-Eintrag.deleted: Soft-Deleted →restore(). Sonst wird ausold(nur echte Spalten) ein neuer Datensatz gebaut undsaveQuietlygespeichert. Eintragrestored. Gelöschte Relationen kommen nicht zurück.created: Eintragrevertedmitdeleted: true, danndelete().attached/detached:RelationActivity::reverse(), dannreverted.- Zum Schluss bekommt der ursprüngliche Eintrag
properties.reverted_at, damit er nicht zweimal zurückgenommen wird.
Datensätze werden ohne Mandanten-Scope und einschließlich Papierkorb gesucht.
Die Filter auf echte Spalten sind wichtig: Ältere Einträge enthalten Formularfelder wie taxonomy_12, die keine Spalten sind. Ein forceFill damit würde das UPDATE scheitern lassen.
Undo am Datensatz: ActivityRevisions
admin/app/Support/ActivityRevisions.php liefert die Aktionen „Rückgängig“ und „Historie“ für Produkte, Artikel und Projekte.
undoable(): die letzten bis zu 5updated-Einträge des Datensatzes, neueste zuerst, ohne zurückgenommene und ohne solche ohne alte Werte.undoLast(): nimmt den neuesten davon, schreibt die alten Werte (nur echte Spalten) still zurück, markiert ihn mitreverted_atund schreibt einenreverted-Eintrag.- „Historie“: die letzten 30 Einträge.
- Keine eigene Rechteprüfung über die der Seite hinaus.
Zusammenspiel: Nach einem „Rückgängig“ am Datensatz gibt es einen neuen reverted-Eintrag. In der Aktivitätenliste zählt dieser als „neuere Änderung“. Ältere updated-Einträge lassen sich dort deshalb erst wieder zurücknehmen, wenn man in der richtigen Reihenfolge vorgeht; am Datensatz geht es weiter, bis 5 Schritte erreicht sind.
Aufbewahrung
logs:archive löscht Einträge älter als 30 Tage (siehe Betrieb). Danach gibt es für diese Änderungen kein Undo mehr.
Tests
admin/tests/Feature/ActivityLogTest.php.