Просмотр исходного кода

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 4 часов назад
Родитель
Сommit
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
 vooraf testen: zet op devbob de datum van één event met een `&` in de titel/zaak
 naar volgende week.
 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
 **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
 custom-module-endpoint bouwt om Views te vervangen.** 3x per endpoint met
 cache-buster op productie: een **custom** Services-resource die twaalf
 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
 tabcategorieën zitten — losse pagina ligt dus voor de hand. De pagina heeft nog
 géén `display_id`-parameter; die moet er eerst op.
 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.**
 **Taak 32 · de 5 komende events die in geen enkele tab komen.**
 *Deel A:* "Tweedehands markt" (nid 214439) raakt geen enkele display — hang 'm
 *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
 kunnen doen, maar dat is een apart onderzoek en niet iets om aan één filter op
 te hangen.
 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).**
 **Taak 34 · twee wezen, gemarkeerd voor de schoonmaak (2026-09-13).**
 `flutterflowmobiel1` services_2 heet nu "ongebruikt" (machine name ongewijzigd,
 `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
 `flutterflow_events` met die van `flutterflowmobiel_establishments` op
 productie.
 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)
 ### Uit taak 18 voortgekomen — voor Bob (hoort bij P2-7 / taak 24)