Преглед изворни кода

Taak 31/33/43/45 afgerond: datumfilter via hook_views_query_alter, dubbele rijen weg, /nl/-prefix, debug-logging achter een vlag

Inzichten naar CLAUDE.md: date_views' exposed datumfilter is stuk onder PHP 8,
isset($obj[key]) op een stdClass is daar fataal (500 per record), plus twee
meetvalkuilen (HTTP-status meelezen bij pagineren; een 200 op pagina 0 bewijst
niets).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob пре 2 часа
родитељ
комит
b4b26fd312
2 измењених фајлова са 89 додато и 73 уклоњено
  1. 47 0
      CLAUDE.md
  2. 42 73
      TASKS.md

+ 47 - 0
CLAUDE.md

@@ -3248,6 +3248,53 @@ devbob's content is oud: 3 records op s1/s2/s5 en **0** op de andere vier, zonde
 vooraf testen: zet op devbob de datum van één event met een `&` in de titel/zaak
 naar volgende week.
 
+**⚠️ date_views' exposed datumfilter overleeft PHP 8 NIET — bouw een
+datumgrens in `hook_views_query_alter()`, niet in de Views-UI.** Bewezen
+2026-09-15 op `flutterflowmobiel1`, na het filter volgens het boekje te hebben
+aangemaakt (Text-widget, granularity Hour, eigen identifier):
+- `?datum_van=2026-09-21` (platte string) → **HTTP 500**, `TypeError: Cannot
+  access offset of type string on string` in
+  `date_views_filter_handler_simple->date_parts_form()` regel 456. In PHP 7 was
+  `$string['value']` een warning, in PHP 8 is het fataal.
+- `?datum_van[value][date]=…` (de vorm die het `date_text`-element wél
+  verwacht) → HTTP 200 maar **nul rijen**, óók met een grens waar élk record
+  aan voldoet. Het filter bouwt een onmogelijke WHERE.
+**Werkende aanpak**, en meteen korter werk: één blok in
+`custom_views_query_alter()` dat `$_GET['datum_van']`/`['datum_tot']` leest,
+valideert op `JJJJ-MM-DD[ UU:MM:SS]`, Europe/Amsterdam → UTC omrekent (Drupal
+slaat `field_date` in UTC op) en `$query->add_where(0, $alias .
+'.field_date_value', $utc, '>=' | '<=')` doet, met
+`$query->ensure_table('field_data_field_date')` voor de alias. Dekt **alle
+displays van de view tegelijk** — en dat scheelt hier 12 wizards, want zes van
+de zeven services-displays zetten `defaults['filters'] = FALSE` en erven dus
+niets van de Master. Let op: de For-dropdown heet daarom "All displays (except
+overridden)" en bereikt die zes niet.
+
+**Een leeg veld op één record kan een heel endpoint 500 geven: `isset($obj[$key])`
+op een stdClass is in PHP 8 fataal.** Gevonden 2026-09-15. Het patroon
+`if (isset($item->$field)) {...} elseif (isset($item[$field])) {...}` in
+`custom_services_request_postprocess_alter()` lijkt onschuldig: zolang de
+object-property gezet is komt de `elseif` nooit aan de beurt. Is de waarde
+`null` (hier een event zonder `plaats`), dan wél — en dan crasht het request.
+Herkenbaar doordat het **per record** gebeurt: één pagina van de zeven geeft
+500, de rest 200. Diagnose in één stap: draai hetzelfde venster over alle
+displays; de blokken mét de guard (`is_object($item) && …` / `is_array($item)
+&& …`) geven 200, die zonder geven 500. Schrijf die guard dus altijd voluit.
+**Waarom dit ertoe doet voor de app:** FlutterFlow's infinite scroll stopt bij
+de eerste pagina die niet laadt, dus de gebruiker ziet een stilletjes
+afgekapte lijst zonder foutmelding.
+
+**Paginerend meten: lees de HTTP-status, en stop niet bij de eerste lege
+pagina.** Dezelfde dag kostte dit een uur verwarring — een pagina die 500 gaf
+werd door mijn script als "leeg" geteld, waardoor een filter beurtelings wél en
+niet leek te werken. Tel per pagina de statuscode mee en ga door tot twee
+opeenvolgende lege pagina's.
+
+**Een 200 op pagina 0 bewijst niet dat je filter werkt.** Ook dat kostte een
+ronde: een ongefilterde eerste pagina laadt prima. Gebruik als rookmelder een
+grens waar niets aan kan voldoen (`?datum_van=2030-01-01` → moet `0` items
+geven), en bij een lijst met een `nid`-filter een node die je kent.
+
 **Views is niet de performance-bottleneck — gemeten 2026-09-14, voordat je een
 custom-module-endpoint bouwt om Views te vervangen.** 3x per endpoint met
 cache-buster op productie: een **custom** Services-resource die twaalf

+ 42 - 73
TASKS.md

@@ -531,56 +531,16 @@ op deze display 0 resultaten, wat suggereert dat bezorgers nauwelijks in de zes
 tabcategorieën zitten — losse pagina ligt dus voor de hand. De pagina heeft nog
 géén `display_id`-parameter; die moet er eerst op.
 
-**Taak 31 · dubbele rijen — ⚠️ OORZAAK GEVONDEN, en het is NIET Distinct
-(2026-09-14).** Bob zette `distinct = TRUE` op de Master (staat in de export) en
-het veranderde niets. Dat kan ook niet, en de meting wijst de echte oorzaak aan:
+**Taak 31 · dubbele rijen in `flutterflow_events` — ✅ AF (2026-09-15).**
+Oorzaak was niet Distinct maar twee relationships naar de slideshow-foto's
+(`field_picture2_fid`, `field_pictures_fid`) met elk een **excluded** veld
+(`uri_2` pathc, `uri_3` pathg): één SQL-rij per foto, en `DISTINCT` ziet die
+niet als duplicaat omdat het excluded veld per rij verschilt. Alle vier weg uit
+de Master; de logo-relationships bleven staan. Resultaat op productie: nid
+214439 van **14 rijen / 43.219 bytes naar 1 rij / 3.088 bytes**, met alle 14
+foto's intact. Veld-voor-veld vergeleken over 40 nodes: geen verschil. In de
+app nagekeken op de emulator — detailpagina rendert volledig, geen excepties.
 
-| nid | rijen | foto's in het `fotos`-veld |
-|---|---|---|
-| 214439 | 14 | **14** |
-| 214441 | 3 | **3** |
-| 214420 | 3 | **3** |
-| 214367 | 2 | **2** |
-
-Eén rij per foto. De boosdoeners zijn de twee **relationships** naar de
-slideshow-foto's, `field_picture2_fid` en `field_pictures_fid`, elk met een
-**excluded** veld eraan (`uri_2` "pathc" en `uri_3` "pathg"). Die twee velden
-staan niet in de JSON maar wél in de SQL-SELECT, met per rij een andere
-file-URI — en `DISTINCT` werkt op de complete SQL-rij, dus voor de database
-zijn het geen duplicaten. Daarom is Distinct hier principieel machteloos.
-
-**Hoe erg is het? Gemeten 2026-09-14, en het antwoord is: geen bug, wel
-verspilling.** De view heeft precies **twee** aanroepers, allebei met een `nid`
-(`evenement_component_widget.dart:104` en `event_current_widget.dart:115`) — er
-is nergens een lijstopbouw, dus Bob's constatering dat `nid` exposed is en dat
-dit een per-event-call is, klopt. Beslissend is dat **alle getters van
-`EvenementCall` `$[0].veld` lezen**, expliciet het eerste element, niet `$[:]`.
-De 13 extra rijen worden dus gewoon genegeerd en de detailpagina werkt normaal.
-Wat het wél kost is bandbreedte: nid 214439 levert **42 KB in plaats van 3 KB**
-(214441: 10 KB i.p.v. 3). Alleen nodes met meerdere slideshow-foto's zijn
-geraakt. Dus: opruimen is netjes en scheelt laadtijd op mobiel, maar het is geen
-livegang-blokker.
-
-**Fix (Views UI → `flutterflow_events` → Master):** verwijder de velden
-`uri_2` (pathc) en `uri_3` (pathg) én de relationships `field_picture2_fid` en
-`field_pictures_fid`. Ze zijn overbodig: de foto's komen al uit de **directe**
-velden `field_picture2`/`field_pictures` (formatter `media_content`) — rij 1
-bevat nu al alle 14 URL's. ⚠️ Laat de relationships `field_logo2_fid` en
-`field_logo4_fid` met hun velden `uri`/`uri_1` staan: die leveren het
-`logo`-veld en zijn single-value, dus die vermenigvuldigen niets.
-Controleer na afloop dat `fotos` nog gevuld is én dat nid 214439 één rij geeft.
-
-_Oorspronkelijke (onjuiste) diagnose:_
-
-**Taak 31 · dubbele rijen in `flutterflow_events` → Distinct.** nid 214439 komt
-14x terug, 214441 3x. Alle 14 rijen zijn veld voor veld **identiek** — dus de
-vermenigvuldiging komt van een sort/filter op een multi-value veld dat niet in
-de output zit, vrijwel zeker `field_date` (die markt vindt 14x plaats). Fix:
-Advanced → Query settings → **Distinct**. Werkt dat niet: het datumfilter →
-*Multiple field settings* → "Display all values in the same row". Verificatie:
-`...flutterflow_events.json?display_id=services_1&nid=214439` moet 1 rij geven.
-Geen haast — de app vraagt deze view alleen per `nid` op en bouwt er nooit een
-lijst uit.
 
 **Taak 32 · de 5 komende events die in geen enkele tab komen.**
 *Deel A:* "Tweedehands markt" (nid 214439) raakt geen enkele display — hang 'm
@@ -599,8 +559,10 @@ Bob's vraag bij taak 33: een custom-module-endpoint zou aan die 1,3 s wél iets
 kunnen doen, maar dat is een apart onderzoek en niet iets om aan één filter op
 te hangen.
 
-**Taak 33 · exposed datumfilter (Vandaag / Dit weekend / Deze week), P2-1.**
-Volledig uitgeschreven verderop onder *"Voor Bob — drie Drupal-taken"*.
+**Taak 33 (= taak 21, P2-1) · datumfilter — ✅ AF 2026-09-15.** Het is géén
+exposed Views-filter geworden; zie het uitgewerkte blok verderop onder
+*"Voor Bob — drie Drupal-taken"* voor het contract en waarom de Views-route
+onder PHP 8 doodloopt.
 
 **Taak 34 · twee wezen, gemarkeerd voor de schoonmaak (2026-09-13).**
 `flutterflowmobiel1` services_2 heet nu "ongebruikt" (machine name ongewijzigd,
@@ -933,28 +895,35 @@ fix voor taak 29 dus in dezelfde hoek: vergelijk de veldinstellingen van
 `flutterflow_events` met die van `flutterflowmobiel_establishments` op
 productie.
 
-**Taak 21 (P2-1) · view `flutterflowmobiel1` — datumfilter Vandaag / Dit
-weekend / Deze week.**
-Er staat nu één datumfilter (`>= -2 hours`) dat **niet exposed** is. Voor
-een keuzefilter in de app is een tweede, wél exposed filter nodig:
-1. **Filter criteria** → *+ Add* → **Content: Datum - start date
-   (field_date)** → operator **"Is between"** → vink **"Expose this
-   filter to visitors"** aan.
-2. Zet de **identifier** op iets voorspelbaars, bv. **`datum_van`** en
-   **`datum_tot`** (een between-filter geeft twee invoervelden en dus twee
-   identifiers).
-3. Laat het bestaande `>= -2 hours`-filter gewoon staan — dat blijft de
-   ondergrens zodat verleden events nooit terugkomen.
-4. Doe dit op **alle zeven** de services-displays, of op de Master als de
-   displays hun filters niet overriden. Let op: `services_1`, `_3`, `_4`,
-   `_5`, `_6` en `_7` hebben `defaults['filters'] = FALSE`, dus die
-   **overriden wél** — je moet ze dan stuk voor stuk langs.
-5. **Controleer met een verzonnen parameter** dat het filter echt
-   aankomt (staande regel uit `CLAUDE.md`): Views negeert onbekende
-   query-parameters stil, dus `?onzin_param=123` moet hetzelfde resultaat
-   geven en `?datum_van=…` een ánder. Zonder die controle bewijst een
-   uitkomst niets.
-Daarna kan Claude de drie knoppen in de app bouwen.
+**Taak 21 / 33 (P2-1) · datumfilter — ✅ AF, maar NIET zoals hieronder stond
+(2026-09-15).** Het is *geen* exposed Views-filter geworden. Dat spoor is
+doodgelopen en moet niemand opnieuw inslaan: **date_views' exposed datumfilter
+overleeft PHP 8 niet.** Een platte `?datum_van=2026-09-21` geeft HTTP 500
+(`TypeError: Cannot access offset of type string on string` in
+`date_views_filter_handler_simple->date_parts_form()`, regel 456), en de wél
+correct gevormde `?datum_van[value][date]=…` levert **nul rijen** op — ook met
+een grens waar élk record aan voldoet. Bewezen met vier meetpunten.
+
+**Wat er nu draait:** een blok in `custom_views_query_alter()` in
+`custom.module`, onderaan, onder `if ($view->name == 'flutterflowmobiel1')`. Dat
+leest `$_GET['datum_van']` / `$_GET['datum_tot']`, valideert op
+`JJJJ-MM-DD[ UU:MM:SS]`, rekent Europe/Amsterdam → UTC om en doet
+`$query->add_where(0, <alias>.field_date_value, $utc, '>=' | '<=')`. Eén functie
+dekt **alle zeven displays**; er is niets in de view aangepast.
+
+**Contract voor de app-kant:**
+- parameters **`datum_van`** en **`datum_tot`**, formaat `YYYY-MM-DD HH:MM:SS`
+- **lokale Nederlandse tijd** (niet UTC — de conversie zit in PHP)
+- **beide grenzen inclusief**; "dit weekend" is dus
+  `datum_van=2026-09-19 00:00:00` + `datum_tot=2026-09-20 23:59:59`.
+  Die bovengrens op `23:59:59` en niet op `00:00:00`, anders mis je de zondag
+- een ongeldige of ontbrekende waarde wordt genegeerd (HTTP 200, ongefilterd)
+- het bestaande niet-exposede `>= -2 hours`-filter blijft de ondergrens
+
+**Geverifieerd op productie 2026-09-15:** `datum_van=2030-01-01` → 0 items,
+`datum_tot=2020-01-01` → 0, venster "19 sep hele dag" → uitsluitend 19 sep,
+`onzin_param` ongewijzigd, en een uurscan waarbij elk venster precies de
+evenementen met die getoonde tijd teruggaf (dus de tijdzone klopt).
 
 ### Uit taak 18 voortgekomen — voor Bob (hoort bij P2-7 / taak 24)