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:
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
wordpress:6.7-php8.3-apache su porta 8080mariadb:11 con volume nominato db_data (persistenza DB)phpmyadmin su porta 8081 (debug)wp_data per tutto /var/www/html (persistenza media/temi/altri plugin)./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=1WP_HOME/WP_SITEURL su http://localhost:8080itcca-allieviitcca-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
Tutti i campi salvati come user_meta con prefisso itcca_. Riepilogo delle 41 colonne del CSV INSARRI:
itcca_ins (default INSARRI dalle impostazioni), itcca_cognome, itcca_nome, itcca_sesso (M/F), itcca_nascita_data (Y-m-d), itcca_nascita_luogo, itcca_cfitcca_indirizzo, itcca_civico, itcca_comune, itcca_capuser_email nativo + itcca_cellulareX = età da nascita_data, Anno = anni di pratica da inizioitcca_sede_u, itcca_centro, itcca_ruolo, itcca_grado, itcca_inizio (anno)itcca_quota_uisp, itcca_quota_itcca, itcca_quota_ado (decimal)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)itcca_r (R/N), itcca_a (A/D)itcca_uisp_n, itcca_uisp_ditcca_doc, itcca_animale, itcca_elem, itcca_el_sx, itcca_el_dx, itcca_noteIl 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).
[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:
+, lunghezza 9-15)Validazioni:
^[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.is_email() di WordPress. Controllo unicità su wp_users.user_email.^[0-9]{5}$.^\+?[0-9]{9,15}$, dopo strip degli spazi.Y-m-d, deve essere nel passato e non oltre 120 anni fa.Errori di validazione mostrati inline sotto ogni campo (sia da JS che da PHP al re-render del form dopo submit).
Flusso submit:
allievo, username = slug di cognome.nome, password random + email "imposta password"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.
Due punti di accesso:
Pagina utente standard (/wp-admin/user-edit.php): sezioni a fisarmonica
Nuova pagina "Allievi ITCCA" (/wp-admin/admin.php?page=itcca-allievi)
WP_List_Table con tutte le 41 colonne del CSV, ordinabiliCentro, per R (rinnovi/nuovi), per A (attivi/disattivi), per anno InizioAtt, Pag, Pro, Ric, Onl, R, A)INSARRI.csvPagina impostazioni Allievi ITCCA → Impostazioni:
OAuth Client ID, OAuth Client Secret, Picker API Key (li ricavi da Google Cloud Console, istruzioni nel README)?page=itcca-allievi-oauth, salva access/refresh tokenImplementazione OAuth:
google/apiclienthttps://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)http://localhost:8080/wp-admin/admin.php?page=itcca-allievi-oauth (in produzione cambierai con il dominio)AUTH_KEY e salvati in wp_optionsModello 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.
spreadsheets.values.getcampo: valore_WP → valore_foglio per ogni differenza. Checkbox per riga "applica modifiche", più "seleziona/deseleziona tutto" per categoriaA=D (disattivo)": utile per archiviazione rapida dei non rinnoviuser_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 fogliowp_options → itcca_phase_a_log): data, n. righe lette, n. aggiornate, n. importate, n. impostate inattive, eventuali errori (es. CF duplicati o non validi)Fase B — Push continuo (WordPress → foglio, attiva dopo Fase A)
profile_update / user_register / salvataggio dalla pagina admin "Allievi ITCCA" → enqueue task di pushclass-phase-b-push.php usa la mappatura user_id ↔ riga salvata in user_meta (itcca_sheet_row)spreadsheets.values.update di tutta la rigaspreadsheets.values.append + aggiorna itcca_sheet_rowCF se itcca_sheet_row mancawp_options (itcca_sync_errors) e visibili in admin con bottone "Riprova" per rigaComportamento al cambio anno (selezione di un foglio diverso dal Picker)
spreadsheet_id + nomeitcca_sheet_row: la mappatura riga ↔ user_id del foglio precedente non è più validaIl 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 partenzaFields::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 filtroStruttura 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:
user_meta WP (zero perdita di dati) ma il push usa il nuovo nome CSVGoogleSheets::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:
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.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.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.
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
manage_options per impostazioni, edit_users per pagina allievisanitize_text_field, absint, wp_kses_post, regex CF)wp_privacy_personal_data_exporters e wp_privacy_personal_data_erasersdocker compose up -d → wizard di setup WP su localhost:8080INS: dal CSV è sempre INSARRI, quindi è impostazione globale (default popolato per ogni nuovo utente)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ù righeY-m-d, presentazione in d/m/Y come nel CSV€ 20 solo in vista/exportwp itcca import-csv opzionale per popolare gli allievi esistenti dal CSV che mi hai allegato (utile per testare con dati reali fin da subito)