Skip to content

Commit 944d260

Browse files
committed
Replace session referer with deterministic back URL for Contao 5.7
Contao 5.7 no longer maintains the session based referer that StoreRefererListener wrote; System::getReferer() now derives the back URL from DcaUrlAnalyzer, which cannot resolve dc-general's custom data providers (verified: it returns empty/wrong URLs for MetaModels tables). Build the "back to list" URL deterministically from the current request instead. - Add ViewHelpers::getBackUrl() (route + route params, minus act/id/rt) - Route redirectHome()/redirectCleanHome() through it - Use it in EditMask (saveNclose/saveNback), BackButtonListener, SelectHandler, AbstractPropertyOverrideEditAllHandler and the show view/template - Drop the select flag when leaving select mode - Remove StoreRefererListener and its service registration - Add docs/referer-handling-contao-5.7.md Note: saveNback on nested (parent/child) definitions now targets the record's own list; parent-list traversal is pending a nested test case (see docs).
1 parent 695bb37 commit 944d260

10 files changed

Lines changed: 285 additions & 258 deletions

File tree

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Umsetzungskonzept: Referer-Handling im dc-general (Contao 5.7)
2+
3+
> Status: **alle Schritte 1–5 erledigt.** Static Analysis (Psalm + phpcs PSR12) und
4+
> End-to-End-Klicktest (Playwright) grün.
5+
>
6+
> - [x] 1 – Backend-Test des 5.7-Ist-Verhaltens (Anhang A)
7+
> - [x] 2 – `ViewHelpers::getBackUrl()` eingeführt, `redirectHome/redirectCleanHome` darauf umgestellt
8+
> - [x] 3 – Call-Sites umgestellt: EditMask (saveNclose/saveNback), AbstractPropertyOverrideEditAllHandler, BackButtonListener, SelectHandler, ShowHandler + Show-Template
9+
> - [x] 4 – `StoreRefererListener` + Service entfernt; `_dcg_referer_update` aus metamodels/core routing.yml entfernt (verifiziert: Service weg, Route-Defaults bereinigt)
10+
> - [x] 5 – Psalm (`--no-cache`) + phpcs PSR12 sauber; Playwright-Klicktest grün (Anhang B)
11+
>
12+
> Entscheidung: `GetReferrerEvent` **ersatzlos** aus dem DCG-Navigationspfad genommen
13+
> (Event bleibt in events-contao-bindings bestehen, wird von DCG nur nicht mehr genutzt).
14+
15+
## 1. Ausgangslage / Ursache
16+
17+
Contao 5.7 hat `System::getReferer()` intern umgestellt: Es liest **nicht mehr die
18+
Session** (`session['referer'][refererId]`), sondern baut den Pfad über den neuen
19+
Service `contao.data_container.dca_url_analyzer` (`getTrail()`) aus DCA-Metadaten,
20+
echten DB-Records, `ptable` und Standard-Sorting-Modi auf.
21+
22+
**Konsequenz:**
23+
24+
- `StoreRefererListener` schreibt eine Session, die **niemand mehr liest**
25+
funktionsloses Altlast-Objekt. Entfernen ist funktional risikolos.
26+
- `DcaUrlAnalyzer` ist auf `DC_Table`-Konventionen gebaut → für dc-generals eigene
27+
Data-Provider und dynamische MetaModels-Tabellen (`mm_*`) **nicht verlässlich**.
28+
Deshalb erzeugt DCG seine Back-URLs künftig selbst.
29+
30+
## 2. Fundament existiert bereits
31+
32+
`ViewHelpers::redirectHome()/redirectCleanHome()``determineNewStyleRedirect()`
33+
baut die "zurück zur Liste"-URL bereits **deterministisch aus dem aktuellen Request**
34+
(`_route` + `_route_params` + Query, ohne `act`) und hat einen Legacy-Fallback
35+
(`contao?do=…&table=…[&pid=…]`). Das ist im Kern der gewünschte "DCG-eigene Trail" —
36+
nur an einen `never`-Redirect gekoppelt und ohne URL-String-Rückgabe für Buttons/Links.
37+
38+
## 3. Kernidee: URL-Builder zentralisieren
39+
40+
Neue, wiederverwendbare Methode in `ViewHelpers`, die die URL **zurückgibt** statt zu
41+
redirecten:
42+
43+
```php
44+
public static function getBackUrl(
45+
EnvironmentInterface $environment,
46+
array $cleanNames = [],
47+
?string $targetProvider = null // für saveNback = Parent-Provider
48+
): string
49+
```
50+
51+
Kapselt **beide** Zweige aus der heutigen `determineNewStyleRedirect`/
52+
`determineLegacyRedirect`-Logik:
53+
54+
- **New-Style** (eigene MM-Route, `_route !== 'contao_backend'`):
55+
`router->generate(routeName, params)` mit bereinigten Parametern.
56+
- **Legacy** (`contao_backend`): `contao?do=…&table=…[&pid=…]`.
57+
58+
Parameter-Bereinigung fürs Listen-Ziel: `act` **und** `id` entfernen, `cleanNames`
59+
entfernen, `pid` behalten (= Kind-Liste). Für `saveNback`/`$targetProvider` eine Ebene
60+
hochgehen (Ziel-`table` = Parent-Provider, `pid` entsprechend reduzieren).
61+
62+
`redirectHome()/redirectCleanHome()` werden dünne Wrapper:
63+
64+
```php
65+
self::dispatchRedirect($environment, new RedirectEvent(self::getBackUrl($environment, $cleanNames)));
66+
```
67+
68+
## 4. Die 5 Call-Sites — konkrete Umstellung
69+
70+
| # | Ort | Heute | Neu |
71+
|---|-----|-------|-----|
72+
| 1 | `EditMask::doPersist` `saveNclose` | `GetReferrerEvent``RedirectEvent` | `RedirectEvent(getBackUrl($env))` |
73+
| 2 | `EditMask::doPersist` `saveNback` | `GetReferrerEvent(false, $parentProvider)` | `RedirectEvent(getBackUrl($env, [], $parentProvider))` |
74+
| 3 | `AbstractPropertyOverrideEditAllHandler:90` | `GetReferrerEvent(false, $definition->getName())` → Redirect | `RedirectEvent(getBackUrl($env))` |
75+
| 4 | `BackButtonListener::getReferrerUrl` (`@api`, Listen-Back-Button) | `GetReferrerEvent(true, parent/self)` | `$event->setHref(getBackUrl($env))` |
76+
| 5 | `SelectHandler::getReferrerUrl` (private, Button-Href) | `GetReferrerEvent(...)` | `getBackUrl($env)` |
77+
78+
Zusätzlich **Template**: `dcbe_general_show.html5:25` nutzt `$this->getReferer(true)`
79+
(Contao-`BackendTemplate`-Methode → `System::getReferer()`). → In `ShowHandler` neue
80+
Template-Variable `backHref = ViewHelpers::getBackUrl($environment)` setzen und im
81+
Template `$this->backHref` verwenden.
82+
83+
## 5. Entfernen / Aufräumen
84+
85+
- `src/EventListener/StoreRefererListener.php` **löschen**.
86+
- Service-Registrierung in `src/Resources/config/event_listeners.yml`
87+
(Block `StoreRefererListener`) **entfernen**.
88+
- In **metamodels/core** `.../Resources/config/routing.yml`: die wirkungslosen
89+
`_dcg_referer_update: true`-Defaults entfernen (4 Vorkommen).
90+
*(Anderes Repo/Paket — separater Commit/PR.)*
91+
92+
## 6. Bewusst nicht anfassen (BC)
93+
94+
- `GetReferrerEvent` + `SystemSubscriber::handleGetReferer` liegen in
95+
**events-contao-bindings** und funktionieren weiter (jetzt via DcaUrlAnalyzer).
96+
Bleiben öffentliche API — dc-general nutzt sie nur intern nicht mehr für die eigene
97+
Navigation.
98+
- **Offen:** `GetReferrerEvent` als optionalen Override-Hook in `getBackUrl`
99+
voranstellen — oder ersatzlos aus dem DCG-Navigationspfad nehmen? *(noch zu entscheiden)*
100+
- `BackButtonListener` bleibt `@api`-Klasse mit gleicher Signatur, nur interne
101+
URL-Quelle ändert sich.
102+
103+
## 7. Offene Punkte / zu testen
104+
105+
1. **5.7-Verhalten ist ungetestet** → Backend-Durchlauf im
106+
`metamodels-devstack-5x-backend-1`-Container: Verhält sich `System::getReferer()`
107+
unter DCG falsch/leer? Referenz-URLs zum Abgleich sammeln. **(Schritt 1, läuft)**
108+
2. **`id`-Bereinigung**: heutiges `determineNewStyleRedirect` entfernt nur `act`,
109+
behält `id`; Legacy-Zweig droppt `id`. `getBackUrl` muss `id` konsistent entfernen
110+
— Nichtregression für bestehende `redirectHome`-Nutzer (Delete/Paste/Select) prüfen.
111+
3. **saveNback-Ebenenlogik**: Parent-Provider → Ziel-`table`/`pid`, auch bei
112+
mehrstufigen Parent/Child-Beziehungen.
113+
4. **`popup`-/`picker`-Modus** und **Ampersand-Encoding** im URL-Builder abbilden.
114+
115+
## 8. Reihenfolge
116+
117+
1. Backend-Test des Ist-5.7-Verhaltens (7.1) → dokumentieren.
118+
2. `ViewHelpers::getBackUrl()` einführen + `redirectHome/redirectCleanHome` darauf
119+
umstellen (additiv, testbar).
120+
3. Call-Sites 1–5 + Show-Template umstellen.
121+
4. `StoreRefererListener` + Service + Routing-Defaults entfernen.
122+
5. Psalm (`--no-cache`, backend-Container) + Backend-Klicktest: Edit→Speichern-und-
123+
zurück, Listen-Back-Button, Show-Back, Select-Modus, EditAll.
124+
125+
## Anhang A: Backend-Test-Ergebnisse (5.7-Ist-Verhalten)
126+
127+
**Umgebung:** Contao Managed Edition 5.7.9 (dev), Container
128+
`metamodels-devstack-5x-backend-1`. MM-Datenansicht ist bereits eine New-Style-Route:
129+
`/contao/metamodel/mm_employees` (kein klassisches `contao?do=…`).
130+
131+
**Probe (auth-frei, Kernel gebootet, `DcaUrlAnalyzer` direkt) für `mm_employees`:**
132+
133+
| Aufruf | Ergebnis | Bewertung |
134+
|--------|----------|-----------|
135+
| `getEditUrl('mm_employees', 1)` | `NULL` | Analyzer findet **kein** Backend-Modul für die Tabelle |
136+
| `getViewUrl('mm_employees', 1)` | `NULL` | dito |
137+
| `getTrail(edit-context)` | 1 Item → `{"label":"","url":"/contao?do=metamodels"}` | **falsches Ziel**: zeigt auf das MM-*Konfig*-Modul, nicht auf die `mm_employees`-Liste |
138+
139+
**Ursache (verifiziert):**
140+
141+
- Kein `$GLOBALS['BE_MOD']`-Eintrag führt `mm_employees` in seiner `tables`-Liste.
142+
- Einziges `metamodel*`-Modul: `metamodels/metamodels` mit `tables=[tl_metamodel_notelist]`
143+
(= Modell-Konfiguration). Die **per-Modell-Datenansichten** (`mm_*`) sind New-Style-
144+
Routen und **keine** klassischen `BE_MOD`-Module mit `tables`-Array.
145+
- `DcaUrlAnalyzer` ist genau auf diese `BE_MOD['…']['tables']`-Konvention gebaut →
146+
kann MM-Datenansichten nicht auflösen und fällt auf `do=metamodels` (Konfig) zurück.
147+
148+
**Fazit:** `System::getReferer()` / `DcaUrlAnalyzer` liefern für MetaModels-Datenansichten
149+
**keine korrekten Back-URLs** (leer bzw. auf das falsche, übergeordnete Konfig-Modul).
150+
Damit ist der DCG-eigene Trail bestätigt notwendig.
151+
152+
**Korrektes Ziel** (Referenz für `getBackUrl`): Aus einer Edit-/Show-Ansicht von
153+
`mm_employees` muss die Back-URL auf die Liste `/contao/metamodel/mm_employees` zeigen
154+
(bzw. bei Kind-Listen auf die entsprechende Parent-Route).
155+
156+
**Route-Struktur (verifiziert per Router-Match):**
157+
158+
Edit-URL (Adresszeile, real):
159+
```
160+
/contao/metamodel/mm_employees?act=edit&id=mm_employees::1&rt=<token>
161+
```
162+
163+
`router->match('/contao/metamodel/mm_employees')`:
164+
```
165+
_route = metamodels.metamodel
166+
_controller = MetaModels\CoreBundle\Controller\Backend\MetaModelController
167+
_scope = backend
168+
_dcg_referer_update = 1
169+
_token_check = 1
170+
tableName = mm_employees (Modellname als ROUTE-Param!)
171+
```
172+
173+
Unterschiede zu Contao-Core (wichtig für den Builder):
174+
175+
- Eigene Route `metamodels.metamodel` statt `contao_backend`; Modellname steckt im
176+
**Route-Param `tableName`**, nicht in `?table=`.
177+
- ID ist dc-generals **serialisierte ModelId** `id=mm_employees::1`, nicht `?id=<int>`.
178+
- Aktion in der Query: `act=edit` (+ `rt`-Token).
179+
180+
**Back-URL-Bildung konkret:**
181+
`router->generate('metamodels.metamodel', ['tableName' => 'mm_employees'])`
182+
= `/contao/metamodel/mm_employees`
183+
→ Route + `_route_params` (`tableName`) behalten; Query **`act`, `id`, `rt`** (+ `cleanNames`) entfernen.
184+
185+
**Korrektur zu Punkt 7.2:** Core-`determineNewStyleRedirect` entfernt heute nur `act`
186+
und ließe `id`/`rt` stehen. `getBackUrl` muss `id` **und** `rt` mit strippen.
187+
188+
**Erweiterung zu Abschnitt 5 (Cleanup):** `_dcg_referer_update` hängt **auch an der
189+
`metamodels.metamodel`-Route** (nicht nur an den `add_all`-Routen). Cleanup-Scope in
190+
metamodels/core entsprechend größer.
191+
192+
**Noch offen:** Kind-/Parent-Listen-Route (saveNback, mehrstufig) — Routen-/`pid`-Struktur
193+
bei verschachtelten MM noch am Live-Backend zu bestätigen. `mm_employees` ist flach:
194+
`saveNback` = `saveNclose` (beide → Liste).
195+
196+
## Anhang B: End-to-End-Verifikation (Playwright, eingeloggtes Backend)
197+
198+
Getestet gegen `http://localhost:8025`, Modell `mm_employees` (flach). Ergebnis nach
199+
dem Umbau:
200+
201+
| Ansicht / Aktion | Back-/Redirect-Ziel | OK |
202+
|------------------|---------------------|----|
203+
| EDIT „Zurück" (`header_back dcg`) | `/contao/metamodel/mm_employees` ||
204+
| SHOW „Zurück" (`header_back dcg`) | `/contao/metamodel/mm_employees` ||
205+
| „Speichern und schließen" (saveNclose) | Redirect → `/contao/metamodel/mm_employees` ||
206+
| „Speichern und zurück" (saveNback) | Redirect → `/contao/metamodel/mm_employees` ||
207+
| Select-Modus „Beenden" | `/contao/metamodel/mm_employees` ||
208+
209+
Alle Ziele sauber, ohne stale `id`/`rt`.
210+
211+
**Fund + Fix während des Tests:** Der Select-Modus-„Beenden"-Button behielt zunächst
212+
`?select=models`. `SelectHandler::getReferrerUrl()` gibt jetzt `getBackUrl($env, ['select'])`
213+
mit — analog zum früheren `redirectCleanHome(['select'])`.
214+
215+
Static Analysis: Psalm (`--no-cache`) „No errors", phpcs PSR12 ohne Beanstandung auf
216+
allen geänderten Dateien.

src/Contao/View/Contao2BackendView/ActionHandler/SelectHandler.php

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
namespace ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\ActionHandler;
2525

2626
use ArrayObject;
27-
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
28-
use ContaoCommunityAlliance\Contao\Bindings\Events\System\GetReferrerEvent;
2927
use ContaoCommunityAlliance\DcGeneral\Action;
3028
use ContaoCommunityAlliance\DcGeneral\Clipboard\ClipboardInterface;
3129
use ContaoCommunityAlliance\DcGeneral\Clipboard\Filter;
@@ -560,23 +558,8 @@ private function getSelectAction(EnvironmentInterface $environment)
560558
*/
561559
private function getReferrerUrl(EnvironmentInterface $environment)
562560
{
563-
$definition = $environment->getDataDefinition();
564-
assert($definition instanceof ContainerInterface);
565-
566-
$parentDefinition = $environment->getParentDataDefinition();
567-
$event = new GetReferrerEvent(
568-
true,
569-
(null !== $parentDefinition)
570-
? $parentDefinition->getName()
571-
: $definition->getName()
572-
);
573-
574-
$dispatcher = $environment->getEventDispatcher();
575-
assert($dispatcher instanceof EventDispatcherInterface);
576-
577-
$dispatcher->dispatch($event, ContaoEvents::SYSTEM_GET_REFERRER);
578-
579-
return $event->getReferrerUrl();
561+
// Leaving the select mode must return to the plain list, so drop the select flag.
562+
return ViewHelpers::getBackUrl($environment, ['select']);
580563
}
581564

582565
/**

src/Contao/View/Contao2BackendView/ActionHandler/ShowHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ protected function process(Action $action, EnvironmentInterface $environment)
347347
$template = (new ContaoBackendViewTemplate('dcbe_general_show'))
348348
->set('headline', $this->getHeadline($translator, $model))
349349
->set('arrFields', $data['values'])
350-
->set('arrLabels', $data['labels']);
350+
->set('arrLabels', $data['labels'])
351+
->set('backHref', ViewHelpers::getBackUrl($environment));
351352

352353
$controller = $environment->getController();
353354
assert($controller instanceof ControllerInterface);

src/Contao/View/Contao2BackendView/EditMask.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
2929
use ContaoCommunityAlliance\Contao\Bindings\Events\Backend\AddToUrlEvent;
3030
use ContaoCommunityAlliance\Contao\Bindings\Events\Controller\RedirectEvent;
31-
use ContaoCommunityAlliance\Contao\Bindings\Events\System\GetReferrerEvent;
3231
use ContaoCommunityAlliance\Contao\Bindings\Events\System\LogEvent;
3332
use ContaoCommunityAlliance\DcGeneral\Clipboard\ClipboardInterface;
3433
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetEditMaskSubHeadlineEvent;
@@ -729,9 +728,10 @@ protected function handleSubmit(ModelInterface $model)
729728
} elseif ($inputProvider->hasValue('saveNclose')) {
730729
$this->clearBackendStates();
731730

732-
$newUrlEvent = new GetReferrerEvent();
733-
$dispatcher->dispatch($newUrlEvent, ContaoEvents::SYSTEM_GET_REFERRER);
734-
$dispatcher->dispatch(new RedirectEvent($newUrlEvent->getReferrerUrl()), ContaoEvents::CONTROLLER_REDIRECT);
731+
$dispatcher->dispatch(
732+
new RedirectEvent(ViewHelpers::getBackUrl($environment)),
733+
ContaoEvents::CONTROLLER_REDIRECT
734+
);
735735
} elseif ($inputProvider->hasValue('saveNcreate')) {
736736
$this->clearBackendStates();
737737
$after = ModelId::fromModel($model);
@@ -745,14 +745,13 @@ protected function handleSubmit(ModelInterface $model)
745745
} elseif ($inputProvider->hasValue('saveNback')) {
746746
$this->clearBackendStates();
747747

748-
$definition = $environment->getDataDefinition();
749-
assert($definition instanceof ContainerInterface);
750-
751-
$parentProviderName = $definition->getBasicDefinition()->getParentDataProvider();
752-
$newUrlEvent = new GetReferrerEvent(false, $parentProviderName);
753-
754-
$dispatcher->dispatch($newUrlEvent, ContaoEvents::SYSTEM_GET_REFERRER);
755-
$dispatcher->dispatch(new RedirectEvent($newUrlEvent->getReferrerUrl()), ContaoEvents::CONTROLLER_REDIRECT);
748+
// Note: for nested (parent/child) definitions this currently targets the list
749+
// the record belongs to. Parent-list traversal is handled once a nested test
750+
// case is available - see docs/referer-handling-contao-5.7.md.
751+
$dispatcher->dispatch(
752+
new RedirectEvent(ViewHelpers::getBackUrl($environment)),
753+
ContaoEvents::CONTROLLER_REDIRECT
754+
);
756755
}
757756
}
758757

src/Contao/View/Contao2BackendView/EventListener/BackButtonListener.php

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@
2121

2222
namespace ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\EventListener;
2323

24-
use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
25-
use ContaoCommunityAlliance\Contao\Bindings\Events\System\GetReferrerEvent;
2624
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait;
2725
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetGlobalButtonEvent;
28-
use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface;
26+
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\ViewHelpers;
2927
use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface;
3028
use ContaoCommunityAlliance\DcGeneral\InputProviderInterface;
31-
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
3229

3330
/**
3431
* This handles the back button event in list views.
@@ -89,23 +86,7 @@ public function handle(GetGlobalButtonEvent $event)
8986
*/
9087
private function getReferrerUrl(EnvironmentInterface $environment)
9188
{
92-
$definition = $environment->getDataDefinition();
93-
assert($definition instanceof ContainerInterface);
94-
95-
$dispatcher = $environment->getEventDispatcher();
96-
assert($dispatcher instanceof EventDispatcherInterface);
97-
98-
$parent = $environment->getParentDataDefinition();
99-
$event = new GetReferrerEvent(
100-
true,
101-
(null !== $parent)
102-
? $parent->getName()
103-
: $definition->getName()
104-
);
105-
106-
$dispatcher->dispatch($event, ContaoEvents::SYSTEM_GET_REFERRER);
107-
108-
$url = $event->getReferrerUrl();
89+
$url = ViewHelpers::getBackUrl($environment);
10990

11091
return \str_starts_with($url, '/') ? $url : '/' . $url;
11192
}

0 commit comments

Comments
 (0)