Newer
Older
itcca-allievi / .cursor / plans / itcca_allievi_wp_plugin.plan.md

name: itcca allievi wp plugin overview: "Plugin WordPress per gestire le iscrizioni ai corsi di Tai Chi: estende l'utente WP con ~40 campi (mappati sul CSV INSARRI), form pubblico via shortcode, gestione completa nel backoffice e sincronizzazione bidirezionale con un Google Sheet privato via OAuth 2.0. Tutto sviluppato e testato in stack Docker locale persistente." todos:

  • id: docker content: Scrivere docker-compose.yml + .env + .gitignore + README con istruzioni Google Cloud Console (OAuth client) status: completed
  • id: plugin-bootstrap content: Creare scheletro plugin (itcca-allievi.php, class-plugin.php, composer.json con google/apiclient) e attivazione/disattivazione status: completed
  • id: fields-roles content: Implementare class-fields.php (registro centrale dei 41 meta + tipi/validatori/sezioni) e class-roles.php (ruolo allievo) status: completed
  • id: validators content: "Validatori: codice fiscale con checksum, CAP, telefono, sesso M/F, date" status: completed
  • id: user-admin content: "Estensione pagina utente WP: sezioni a fisarmonica per ogni gruppo di campi + età/anni calcolati read-only" status: completed
  • id: allievi-list content: Pagina admin Allievi ITCCA con WP_List_Table (tutte le colonne CSV, sort, filtri, quick-edit flag) status: completed
  • id: form-public content: "Shortcode [itcca_iscrizione]: template, CSS con variabili che ereditano dal tema, JS di validazione client, handler POST con creazione utente e nonce/honeypot" status: completed
  • id: settings content: "Pagina impostazioni: Google client ID/secret, lista centri (per dropdown admin), testo privacy form, redirect post-iscrizione, colore primario" status: completed
  • id: google-oauth content: "Implementare flow OAuth 2.0 (scope drive.file + spreadsheets): connect, callback, salvataggio token cifrati, refresh automatico" status: completed
  • id: google-picker content: "Integrare Google Picker API: bottone 'Seleziona foglio da Drive', salva spreadsheet_id+nome, select tab, storico fogli connessi" status: completed
  • id: phase-a-align content: "Fase A (foglio → WP): importer con match per CF, report diff, preview con checkbox, apply su user_meta + creazione nuovi allievi" status: completed
  • id: phase-b-push content: "Fase B (WP → foglio): hook on save, mappatura user_id ↔ riga, retry via WP-Cron, log errori in admin, bottone manuale 'Pusha tutti'" status: completed
  • id: export-csv content: Export CSV nel formato originale INSARRI + WP-CLI import comando per popolazione iniziale status: completed
  • id: gdpr content: Hook GDPR exporter/eraser e rate limiting form pubblico status: completed
  • id: test-docker content: Avviare lo stack, completare setup WP, configurare OAuth, fare end-to-end test (iscrizione → utente → riga Sheet) status: completed
  • id: schema-reconcile content: "Riconciliazione schema da foglio: refactor Fields (baseline + overrides), nuova SchemaReconcile (diff + fuzzy match + UI), routing Picker→pagina schema, sezione 'Campi rimossi (storico)' nell'edit utente, esclusione dei deleted dall'export CSV e dal push Fase B" status: completed
  • id: zodiac-elements content: "Dropdown Animale (zodiaco cinese auto-calcolato + override) ed Elemento/Elemento sx/Elemento dx con palette colori; visibilità in lista; Dashboard con 4 grafici a torta inline SVG" status: completed
  • id: oauth-wizard content: "Wizard di setup Google OAuth in 5 step con link diretti a Cloud Console, validazione live delle credenziali e copia redirect URI" status: completed
  • id: dashboard-kpi-age content: "KPI età sulla Dashboard: card Età media, Più giovane (nome+anni), Più anziano (nome+anni) e bar chart nascite per mese" status: completed
  • id: sedi-crud content: "CRUD Sedi di insegnamento in Settings (nome + indirizzo + lat/lng), migrazione da itcca_centri, integrazione con dropdown Centro" status: completed
  • id: geocoder content: "Servizio Geocoder via OSM Nominatim (rate-limit 1 req/sec, cache hash indirizzo, User-Agent dedicato, retry tramite endpoint AJAX a singolo step)" status: completed
  • id: dashboard-map content: "Mappa Leaflet sotto le torte con marker sedi (icona ★) + marker residenze allievi (●), bottone batch-geocode con progress, layer toggle" status: completed
  • id: sedi-abandoned content: "Flag 'Abbandonata' sulle Sedi: checkbox in tabella Settings + riga visiva 'disattivata' + marker grigio in scala di grigi sulla mappa + layer dedicato 'Sedi abbandonate'" status: completed
  • id: active-flag content: "Stato attivo/inattivo derivato dai tab del foglio (INSARRI = attivi, ZZZ auto-rilevato = inattivi): meta itcca_active + itcca_sheet_tab, Fase A dual-tab con anteprima e log esteso, Fase B routing sul tab giusto senza spostare righe, views Attivi/Inattivi/Tutti nella lista admin e nella dashboard, marker inattivi differenziati sulla mappa" status: completed

    isProject: false

Stack e cartella di lavoro

Tutto in /Users/fabio.arrigoni/Studio/Itcca-allievi/:

Itcca-allievi/
├── .cursor/
│   └── plans/
│       └── itcca_allievi_wp_plugin.plan.md   # questo file (fonte di verità del piano)
├── docker-compose.yml
├── .env                           # secrets DB
├── .gitignore
├── README.md                      # setup & istruzioni Google Cloud
└── plugin/
    └── itcca-allievi/             # montato in wp-content/plugins

Docker stack

  • wordpress:6.7-php8.3-apache su porta 8080
  • mariadb:11 con volume nominato db_data (persistenza DB)
  • phpmyadmin su porta 8081 (debug)
  • Volume nominato wp_data per tutto /var/www/html (persistenza media/temi/altri plugin)
  • Bind-mount di ./plugin/itcca-allievi su /var/www/html/wp-content/plugins/itcca-allievi (hot-reload del codice)
  • .env con DB_ROOT_PASSWORD, DB_PASSWORD, WP_DEBUG=1
  • WP_HOME/WP_SITEURL su http://localhost:8080

Plugin itcca-allievi

Struttura file

itcca-allievi/
├── itcca-allievi.php              # main file + bootstrap
├── composer.json                  # google/apiclient ^2.18
├── vendor/                        # via composer install
├── includes/
│   ├── class-plugin.php           # singleton, registrazione hook
│   ├── class-fields.php           # registro centrale dei meta + tipi
│   ├── class-roles.php            # ruolo "allievo"
│   ├── class-validators.php       # CF (regex+checksum), CAP, telefono, email
│   ├── class-form-shortcode.php   # [itcca_iscrizione] + handler POST
│   ├── class-user-admin.php       # sezioni edit utente + colonne list table
│   ├── class-allievi-list.php     # pagina "Allievi" con tutte le colonne CSV
│   ├── class-settings.php         # pagina impostazioni
│   ├── class-google-oauth.php     # OAuth flow + token refresh
│   ├── class-google-picker.php    # Google Picker UI per selezione foglio da Drive
│   ├── class-google-sheets.php    # read/append/update righe via Sheets API
│   ├── class-sheet-history.php    # storico fogli usati negli anni
│   ├── class-schema-reconcile.php # riconciliazione schema (diff colonne foglio vs WP)
│   ├── class-phase-a-align.php    # Fase A: importer foglio → WP con diff/preview
│   ├── class-phase-b-push.php     # Fase B: push WP → foglio on save + retry
│   └── class-export-csv.php       # export formato INSARRI
├── assets/
│   ├── css/form.css               # variabili CSS che ereditano dal tema
│   ├── css/admin.css
│   └── js/form.js                 # validazione client + calcolo età live
└── templates/
    └── form-iscrizione.php

Mappatura campi CSV → user meta

Tutti i campi salvati come user_meta con prefisso itcca_. Riepilogo delle 41 colonne del CSV INSARRI:

  • Anagrafica: itcca_ins (default INSARRI dalle impostazioni), itcca_cognome, itcca_nome, itcca_sesso (M/F), itcca_nascita_data (Y-m-d), itcca_nascita_luogo, itcca_cf
  • Residenza: itcca_indirizzo, itcca_civico, itcca_comune, itcca_cap
  • Contatti: usa user_email nativo + itcca_cellulare
  • Calcolati (non salvati): X = età da nascita_data, Anno = anni di pratica da inizio
  • Tesseramento: itcca_sede_u, itcca_centro, itcca_ruolo, itcca_grado, itcca_inizio (anno)
  • Quote: itcca_quota_uisp, itcca_quota_itcca, itcca_quota_ado (decimal)
  • Ricevuta/pagamento: itcca_pag_chi, itcca_pag_il, itcca_ric_il, itcca_ric_n, itcca_att, itcca_pag, itcca_pro, itcca_ric, itcca_onl (i 5 flag come booleani)
  • Stato: itcca_r (R/N), itcca_a (A/D)
  • UISP card: itcca_uisp_n, itcca_uisp_d
  • Altro: itcca_doc, itcca_animale, itcca_elem, itcca_el_sx, itcca_el_dx, itcca_note

Il registro è centralizzato in class-fields.php in modo che list table, edit page, form e Google Sheets condividano la stessa fonte di verità (label, tipo, validatore, sezione, visibilità nel form pubblico).

Form pubblico (shortcode [itcca_iscrizione])

Tutti i campi sono obbligatori (asterisco rosso in UI, attributo HTML required, ricontrollo server-side). La sede del corso e tutti gli altri campi CSV non elencati qui li compili tu manualmente nel backoffice.

Campi visibili al pubblico:

  • Cognome (obbligatorio, testo)
  • Nome (obbligatorio, testo)
  • Sesso (obbligatorio, radio M / F)
  • Data di nascita (obbligatorio, date picker, deve essere data valida nel passato)
  • Comune di nascita (obbligatorio, testo libero, es. "Osio Sotto-BG")
  • Codice fiscale (obbligatorio, validato lato client e server)
  • Indirizzo di residenza (obbligatorio, testo)
  • Numero civico (obbligatorio, testo breve)
  • Comune di residenza (obbligatorio, testo)
  • CAP (obbligatorio, esattamente 5 cifre)
  • Email (obbligatorio, validato lato client e server)
  • Cellulare (obbligatorio, solo cifre + opzionale prefisso +, lunghezza 9-15)
  • Checkbox consenso privacy + liberatoria (obbligatorio, testo configurabile dalle impostazioni)
  • Honeypot anti-bot nascosto + nonce WordPress (non visibili all'utente)

Validazioni:

  • Codice fiscale: client-side regex ^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$ + server-side checksum algoritmo ufficiale (carattere 16 calcolato dai primi 15 secondo tabella ministeriale). Inoltre controllo unicità: due iscritti non possono avere lo stesso CF.
  • Email: client-side regex HTML5 + server-side is_email() di WordPress. Controllo unicità su wp_users.user_email.
  • CAP: regex ^[0-9]{5}$.
  • Cellulare: regex ^\+?[0-9]{9,15}$, dopo strip degli spazi.
  • Data di nascita: parsing in Y-m-d, deve essere nel passato e non oltre 120 anni fa.
  • Coerenza CF ↔ altri campi: se il sesso/data nascita/comune nascita estraibili dal CF non corrispondono ai dati inseriti, mostro un warning ma NON blocco (in alcuni casi limite il CF storico può divergere). Il blocco lo facciamo solo sui dati malformati, non sulle incongruenze.

Errori di validazione mostrati inline sotto ogni campo (sia da JS che da PHP al re-render del form dopo submit).

Flusso submit:

  1. Validazione client-side immediata (JS) per CF, email, CAP, cellulare, campi required
  2. Validazione server-side al POST (tutta, indipendentemente dal client)
  3. Crea utente WP con ruolo allievo, username = slug di cognome.nome, password random + email "imposta password"
  4. Salva tutti i meta
  5. Email notifica all'admin
  6. Push riga su Google Sheet (vedi sotto), con retry differito via WP-Cron se la chiamata fallisce
  7. Redirect a pagina di conferma (configurabile)

Tema grafico: il CSS usa currentColor, variabili CSS (--itcca-primary) e classi WP standard (.wp-block-button, ecc.) per ereditare dal tema attivo. Una sezione "Aspetto" nelle impostazioni permette di sovrascrivere il colore primario. Se vuoi una resa pixel-perfect del tema vivo, dopo la prima iterazione mi passi URL o screenshot e raffino.

Backoffice WordPress

Due punti di accesso:

  1. Pagina utente standard (/wp-admin/user-edit.php): sezioni a fisarmonica

    • Anagrafica, Residenza, Tesseramento, Quote, Ricevuta/Pagamento, Stato, UISP card, Note
    • Età e anni di pratica mostrati come read-only calcolati
  2. Nuova pagina "Allievi ITCCA" (/wp-admin/admin.php?page=itcca-allievi)

    • WP_List_Table con tutte le 41 colonne del CSV, ordinabili
    • Filtri rapidi: per Centro, per R (rinnovi/nuovi), per A (attivi/disattivi), per anno Inizio
    • Ricerca per cognome/nome/CF
    • Quick-edit inline per i flag (Att, Pag, Pro, Ric, Onl, R, A)
    • Azione bulk: "Sincronizza con Google Sheet"
    • Bottone "Esporta CSV" che produce un file identico per schema a INSARRI.csv

Integrazione Google Sheets (OAuth 2.0 + Picker)

Pagina impostazioni Allievi ITCCA → Impostazioni:

  • Sezione "Google":
    • Campi OAuth Client ID, OAuth Client Secret, Picker API Key (li ricavi da Google Cloud Console, istruzioni nel README)
    • Bottone "Connetti Google" → avvia OAuth, callback su ?page=itcca-allievi-oauth, salva access/refresh token
  • Sezione "Foglio Google attivo":
    • Bottone "Seleziona foglio da Drive" → apre il Google Picker (esploratore Drive nativo, con ricerca/recenti/Drive condivisi) filtrato sui soli Google Sheets
    • Dopo selezione mostra: nome foglio, ID, link, data ultima Fase A
    • Select del tab (popolato via Sheets API)
    • Mapping colonne ↔ campi (precompilato dal CSV INSARRI, editabile)
    • Bottoni azione: "Riallinea da foglio (Fase A)", "Pusha tutti su foglio (Fase B forzata)", "Test connessione"
  • Sezione "Storico fogli usati":
    • Tabella sola lettura: anno, nome foglio, ID, link Drive, data connessione, data disconnessione, ultima Fase A
    • Bottone per riconnettere un foglio dello storico se necessario

Implementazione OAuth:

  • Composer dipendenza: google/apiclient
  • Tu crei su Google Cloud Console (istruzioni nel README):
    1. Un OAuth Client ID di tipo "Web application"
    2. Una API Key (per il Picker, frontend-only, ristretta per referrer)
  • Scope richiesti:
    • https://www.googleapis.com/auth/spreadsheets (lettura/scrittura del foglio scelto)
    • https://www.googleapis.com/auth/drive.file (scope minimo: accesso ai soli file aperti tramite Picker — più privacy-friendly del drive.readonly)
  • Redirect URI: http://localhost:8080/wp-admin/admin.php?page=itcca-allievi-oauth (in produzione cambierai con il dominio)
  • Access + refresh token cifrati con AUTH_KEY e salvati in wp_options
  • Refresh automatico alla scadenza

Modello di sincronizzazione a due fasi

Il cambio anno è un'operazione esplicita: ogni anno selezioni un foglio nuovo dal Picker. Il foglio del nuovo anno è già pre-popolato manualmente da te, con alcune celle modificate rispetto al precedente. Per gestire questo workflow il plugin opera in due fasi distinte e sequenziali.

Fase A — Allineamento iniziale (foglio → WordPress, one-shot per ogni foglio)

Trigger: bottone "Riallinea da foglio" oppure suggerito automaticamente con avviso subito dopo la selezione di un nuovo foglio dal Picker.

  1. Il plugin scarica tutte le righe del tab attivo via spreadsheets.values.get
  2. Per ogni riga del foglio, cerca un match con un utente WP confrontando il codice fiscale (chiave unica)
  3. Produce un report di diff raggruppato in tre categorie, tutte gestite con scelta caso per caso nell'UI di preview:
    • Da aggiornare in WP (match per CF + celle diverse): tabella campo: valore_WP → valore_foglio per ogni differenza. Checkbox per riga "applica modifiche", più "seleziona/deseleziona tutto" per categoria
    • Solo sul foglio (CF presente in foglio, mancante in WP): anteprima del nuovo utente che verrebbe creato. Checkbox "crea utente WP" per riga (default: deselezionato, lo abiliti tu)
    • Solo in WP (CF presente in WP, mancante sul foglio): tipicamente allievi non rinnovati o rimossi a mano dal foglio. Per ogni riga radio button con 3 azioni:
      • "Lascia stare" (default): WP non viene toccato e l'allievo non viene scritto sul foglio finché tu non salvi modifiche dalla sua scheda
      • "Imposta A=D (disattivo)": utile per archiviazione rapida dei non rinnovi
      • "Pusha sul foglio comunque" (append a fine foglio): se è stato cancellato per errore dal foglio
  4. Pagina di preview con le tre tabelle sopra + pulsante "Applica selezionati" in fondo
  5. Apply: aggiorna user_meta, crea i nuovi utenti (solo quelli flaggati), applica le azioni "Solo in WP" scelte, popola itcca_sheet_row con il numero di riga del foglio per ogni allievo presente sul foglio
  6. Log completo dell'operazione (wp_optionsitcca_phase_a_log): data, n. righe lette, n. aggiornate, n. importate, n. impostate inattive, eventuali errori (es. CF duplicati o non validi)
  7. Marca il foglio come "allineato": da questo momento la Fase B è attiva
  8. Il bottone "Riallinea da foglio (Fase A)" resta sempre disponibile nelle impostazioni: utile quando edito qualche cella a mano sul foglio già connesso e voglio risincronizzare WP senza dover ricollegare il foglio

Fase B — Push continuo (WordPress → foglio, attiva dopo Fase A)

  • Hook su profile_update / user_register / salvataggio dalla pagina admin "Allievi ITCCA" → enqueue task di push
  • class-phase-b-push.php usa la mappatura user_id ↔ riga salvata in user_meta (itcca_sheet_row)
  • Riga esistente → spreadsheets.values.update di tutta la riga
  • Riga mancante (nuovo iscritto dal form pubblico o creato manualmente) → spreadsheets.values.append + aggiorna itcca_sheet_row
  • Match di fallback per CF se itcca_sheet_row manca
  • Errori loggati in wp_options (itcca_sync_errors) e visibili in admin con bottone "Riprova" per riga
  • Bottone manuale "Pusha tutti su foglio" per forzare il re-push completo (utile dopo modifiche bulk)

Comportamento al cambio anno (selezione di un foglio diverso dal Picker)

  1. Picker → memorizza nuovo spreadsheet_id + nome
  2. Sposta il foglio precedente nello "Storico fogli" con timestamp di disconnessione
  3. Reset di tutti gli itcca_sheet_row: la mappatura riga ↔ user_id del foglio precedente non è più valida
  4. Disabilita temporaneamente la Fase B
  5. Selezione del tab → il plugin legge l'header e confronta con lo schema WP attuale. Se diverge, redirect automatico alla pagina di Riconciliazione schema (vedi sezione dedicata) prima di abilitare la Fase A
  6. Mostra banner persistente in admin: "Foglio nuovo connesso — esegui Fase A (Riallinea da foglio) prima di abilitare il push automatico"
  7. Solo dopo aver completato la Fase A almeno una volta sul nuovo foglio, la Fase B torna attiva

Riconciliazione schema (evoluzione delle colonne del foglio)

Il foglio Google può cambiare nel tempo: nuove colonne aggiunte, colonne rinominate, colonne rimosse. Il plugin gestisce questa evoluzione preservando i dati WordPress.

Registro campi dinamico

plugin/itcca-allievi/includes/class-fields.php espone:

  • Fields::baseline(): array hardcoded dei 42 campi INSARRI di partenza
  • Fields::all(): baseline con applicati gli overrides (rename/delete/add)
  • Fields::overrides() / Fields::save_overrides(): unica fonte degli scostamenti, memorizzata in wp_options['itcca_schema_overrides']
  • Fields::deleted_fields(), Fields::active_storable(), Fields::is_deleted(): helper di filtro

Struttura itcca_schema_overrides:

[
  'renames'   => [ 'animale' => ['csv' => 'Zodiaco', 'renamed_at' => '...'] ],
  'deletes'   => [ 'el_dx'   => ['original_csv' => 'El Dx', 'deleted_at' => '...'] ],
  'additions' => [ 'allergie' => ['csv'=>'Allergie','label'=>'...','type'=>'textarea','section'=>'altro','public'=>false,'added_at'=>'...'] ],
]

Effetti automatici

Tutti i consumer del registro (form pubblico, edit utente, list table, export CSV, push Fase B) usano già Fields::all() / Fields::public_fields() / Fields::by_csv(). Conseguenze gratis grazie al refactor:

  • Una colonna rinominata mantiene la stessa user_meta WP (zero perdita di dati) ma il push usa il nuovo nome CSV
  • Una colonna aggiunta diventa un campo WP a tutti gli effetti (incluso opzionalmente il form pubblico)
  • Una colonna rimossa resta editabile in admin in una sezione "Campi rimossi (storico)", ma non viene scritta su Sheet (GoogleSheets::build_row_for_user itera sull'header, non trova il campo perché il CSV non c'è) e non appare nell'export CSV (ExportCsv::csv_header esclude i deleted)

Pagina di riconciliazione

Quando l'utente seleziona un nuovo foglio o cambia tab via Picker, plugin/itcca-allievi/includes/class-google-picker.php legge l'header e confronta con lo schema corrente. Se ci sono differenze viene visualizzata la pagina di Riconciliazione schema (submenu nascosto itcca-allievi-schema, anche rilanciabile manualmente dalle impostazioni con il bottone "Riconcilia schema").

La pagina (plugin/itcca-allievi/includes/class-schema-reconcile.php SchemaReconcile::render_page) mostra tre tabelle:

  1. Colonne mancanti: ogni riga ha radio "Rinominata in [select delle nuove]" oppure "Davvero rimossa (marca deleted_)". Il rename è pre-selezionato in base a una euristica di fuzzy match (similar_text 70% + prossimità di posizione 30%, soglia 60%). I campi pubblici obbligatori del baseline (Cognome, Nome, Sesso, CF, Email, ...) non possono essere marcati come rimossi: l'UI forza il rename.
  2. Colonne nuove: per ognuna non scelta come target di rename, configurazione di label, tipo (text/textarea/date/flag/decimal/year/email/tel), sezione, e flag "visibile nel form pubblico". Se il CSV combacia esattamente con un campo baseline currently-deleted, viene proposto un checkbox "Ripristina" che rimuove la voce da deletes.
  3. Anteprima riepilogo + bottone "Applica e procedi con Fase A" → salva itcca_schema_overrides, resetta aligned_at del foglio attivo, redirect a Fase A.

Flow Picker → Riconciliazione → Fase A

flowchart TD
    pick[Picker: utente seleziona foglio]
    pick --> setSheet[ajax_set_sheet salva spreadsheet_id]
    setSheet --> settings[Settings: scegli tab]
    settings --> tabChosen[ajax_set_tab]
    tabChosen --> readHdr[Leggi header del tab via Sheets API]
    readHdr --> diff{Diff vs schema corrente}
    diff -->|vuoto| phaseA[Fase A - Allineamento dati]
    diff -->|non vuoto| reconcile[Pagina Riconciliazione Schema]
    reconcile --> userConfirm[Utente conferma rename/add/delete]
    userConfirm --> applyOver[Salva itcca_schema_overrides]
    applyOver --> phaseA
    phaseA --> done[Foglio allineato, Fase B attiva]

Reversibilità

Tutte le operazioni sono reversibili rimuovendo l'entry corrispondente da itcca_schema_overrides. I dati in user_meta non vengono mai cancellati dalla riconciliazione: il "delete" è uno snapshot conservativo.

Diagramma del flusso

flowchart TD
    subgraph FaseA["Fase A: foglio -> WP (one-shot ad ogni cambio foglio)"]
        AdminA["Admin Fabio"] -->|"Seleziona foglio"| Picker["Google Picker"]
        Picker -->|"spreadsheet_id"| Settings["Settings + Storico fogli"]
        Settings -->|"Riallinea"| Reader["Sheets API values.get"]
        Reader -->|"match per CF"| Diff["Report diff: aggiorna / importa / solo WP"]
        Diff -->|"preview + conferma"| ApplyA["Apply: update user_meta + crea nuovi allievi"]
        ApplyA --> WPUserA["WP user + user_meta + itcca_sheet_row"]
    end

    subgraph FaseB["Fase B: WP -> foglio (continuo, abilitata dopo Fase A)"]
        Visitatore["Visitatore sito"] -->|"compila"| Form["Shortcode itcca_iscrizione"]
        Form --> WPUserB["WP user + user_meta"]
        AdminB["Admin Fabio"] -->|"edit"| AdminPage["Pagina Allievi ITCCA"]
        AdminPage --> WPUserB
        WPUserB -->|"on save hook"| Queue["class-phase-b-push (queue)"]
        Queue -->|"OAuth tokens"| Writer["Sheets API append/update"]
        Writer --> FoglioGoogle["Foglio dell'anno"]
    end

    FaseA -.->|"abilita"| FaseB

Sicurezza e GDPR

  • Nonces su tutti i form (pubblici e admin)
  • Capability check: manage_options per impostazioni, edit_users per pagina allievi
  • Sanitize/escape rigorosi (sanitize_text_field, absint, wp_kses_post, regex CF)
  • Token OAuth cifrati a riposo
  • Hook GDPR wp_privacy_personal_data_exporters e wp_privacy_personal_data_erasers
  • Honeypot + rate limiting (transient) sul form pubblico

Testing manuale nel Docker

  1. docker compose up -d → wizard di setup WP su localhost:8080
  2. Attiva plugin, completa impostazioni Google
  3. Compila form da pagina con shortcode → verifica utente creato + riga su Sheet
  4. Modifica meta dalla pagina admin → verifica update sulla riga del Sheet
  5. Riavvia container → tutti i dati persistono (volumi nominati)

Note operative

  • INS: dal CSV è sempre INSARRI, quindi è impostazione globale (default popolato per ogni nuovo utente)
  • Campi calcolati X e Anno: derivati al rendering, non salvati. Anno = "anniPratica,etàAlleggerita" come nel CSV (es. 22,38 per uno che ha iniziato nel 2004 ed è nato nel 1975) — confermo la formula esatta in fase di implementazione guardando più righe
  • Date: salvataggio interno in Y-m-d, presentazione in d/m/Y come nel CSV
  • Quote: salvate come decimale, formattazione € 20 solo in vista/export
  • Importazione iniziale: comando WP-CLI wp itcca import-csv opzionale per popolare gli allievi esistenti dal CSV che mi hai allegato (utile per testare con dati reali fin da subito)