- **Aggiunto**: Nuovo tab "Docker" nella pagina `/help` con guida completa per creare un'immagine Docker
- **Contenuto**:
- Due opzioni affiancate: SQLite (leggero/demo) e MySQL (standard/produzione)
- Dockerfile basato su `php:8.4-apache` con pdo_mysql + pdo_sqlite, composer, asset compilati
- Entrypoint script `docker-entrypoint.sh` che rileva automaticamente `DB_CONNECTION` e configura .env di conseguenza; attende MySQL via PDO poll se necessario
- Seeder `DockerBaseDataSeeder` con tipologie documenti (7), tipologie eventi (8) e ruoli (8)
- Docker Compose in due varianti (Opzione A — SQLite, Opzione B — MySQL con healthcheck e volume separato)
- Istruzioni build, avvio con `-f` per scegliere il compose, e creazione primo utente amministratore via CLI
- **Files modificati**:
-`resources/views/help/index.blade.php`: tab-pane `#help-docker` riscritto con doppia opzione SQLite/MySQL
- eventi/create.blade.php: "Dove" card with address + Google Maps URL fields
- eventi/edit.blade.php: "Dove" card with address + Google Maps URL fields
- eventi/show.blade.php: "Luogo" section with address, link to Google Maps, embedded map preview
### Responsabile Multi-select (Completed)
- Migration `2026_05_12_000003_change_responsabile_to_multi.php` changed `responsabile_id` to `responsabile_ids` JSON
- Gruppo model updated with `getResponsabiliIds()` and `getResponsabili()` methods
- GruppoController updated: index(), store(), update(), show() for multi-select
- Views updated: show, edit, create blades with multi-select support
### 13 Maggio 2026 - Fixes
- **Ordinamento colonne**: Aggiunto ordinamento per Descrizione, Tipo, Gruppi, Responsabili
- **Form edit**: Aggiunti campi `tipo_evento` e `note` mancanti
- **Show view**: Corretta struttura HTML della sezione documenti
### 14 Maggio 2026 - Fixes
- **Form annidati**: La sezione Documenti spostata FUORI dal form principale
- **Tipologia enum**: Aggiunti valori 'programma' e 'locandina' alla colonna tipologia in documenti
- **Visibilita enum**: Aggiunto valore 'evento' alla colonna visibilita in documenti
### 15 Maggio 2026 - Eventi Show
- Rimossa sezione "Dove" duplicata dalla sidebar
- Aggiunta nuova sezione "Luogo" sotto "Data e Orario" nella colonna centrale
### 15 Maggio 2026 - Email Settings
- Aggiunto pulsante "Test Invio Email (SMTP)" nella pagina impostazioni email
- Aggiunto metodo `testSmtp()` nel controller per verificare la configurazione SMTP
- Route: `POST /impostazioni/email/test-smtp`
- Campo password opzionale (non più required)
- Rimossi attributi required dai campi del form impostazioni email
### 15 Maggio 2026 - Eventi Calendar
- Nuova vista calendario con FullCalendar 6.1.10
- Routes: `GET /eventi/calendar` e `GET /eventi/calendar/events`
- Metodo `calendar()` e `calendarEvents()` in EventoController
- Sidebar: menu Eventi con sottovoci Calendario, Elenco, Nuovo Evento
- Click su evento apre la pagina show dell'evento
### 15 Maggio 2026 - Email UI
- **Compose view**: Rimossa sidebar sinistra, solo form a schermo intero
- **Sidebar menu Email**:
- Ordine: Nuova Email, In arrivo, Preferiti, Inviate, Bozze, Archivio, Cestino
- Cliccando "Email" apre direttamente inbox
- **Bottone sincronizzazione**: "Ricevi/Invia" nella pagina inbox
- **Sincronizzazione**: Limitata a 100 messaggi per cartella
### 15 Maggio 2026 - Bug Fixes
- **RFC 2822**: Usare `Address::create()` di Symfony per gestire correttamente il display name
- **Route email.sync**: Sostituito `route('email.sync.quick')` con `url('email/sync/quick')` nella vista email/index.blade.php
### 16 Maggio 2026 - Email UI Fixes
- **Footer**: Aggiunto `min-height: calc(100vh - 120px)` al content-wrapper per garantire visibilità footer
- **Pagination**: Creato custom view `vendor.pagination.simple-bootstrap-4` con caratteri `‹` e `›` invece di SVG (problema scalabilità risolto)
### 16 Maggio 2026 - Logo Upload System
- **Model**: `App\Models\AppSetting` con metodi `getLogoUrl()`, `getLogoSmallUrl()`, `getAppName()`
- **Migration**: `2026_05_16_000001_add_logo_path_to_app_settings` - aggiunge `logo_path` e `logo_small_path`
- **Controller**: `ImpostazioniController` - metodi `uploadLogo()` e `removeLogo()`
- **Problema**: Errore "syntax error, unexpected token 'endif'" nella dashboard
- **Causa**: Dopo aver rimosso il blocco `@if(Auth::user()->isSuperAdmin())` dalla sidebar per sostituirlo con `@if(Auth::user()->canAccess('report'))`, erano rimasti un `@endif` orfano (linea 237) e un `</li>` extra (linea 239)
- **Soluzione**:
1. Rimosso `@endif` orfano
2. Rimosso `</li>` extra
3. Aggiunto nuovo `@if(Auth::user()->isSuperAdmin())` per proteggere i menu Impostazioni e Admin
- **Verifica completa**: Scansionati tutti i blade file del progetto - tutte le direttive `@if/@endif`, `@foreach/@endforeach`, `@php/@endphp` sono correttamente bilanciate. Compilazione viste OK.
### 25 Maggio 2026 - Accesso Report Basato su Permessi
- **Modifica**: Il modulo 'report' è stato aggiunto a `User::MODULES` come nuovo modulo permissionabile
- **Funzionalità**: Gli utenti con permesso `canAccess('report')` (livello lettura o superiore) possono accedere alla pagina Report, non più solo il superadmin
- `app/Http/Controllers/DocumentoController.php`: logica esplicita per mappare visibilità → target_type
### 25 Maggio 2026 - Fix Pagina Gruppi (Tabella Pivot Inesistente)
- **Problema**: Pagina gruppi falliva con "Table 'glastree.gruppo_responsabili' doesn't exist"
- **Causa**: Il modello `Gruppo` aveva una relazione `responsabili()` che puntava a tabella pivot inesistente (la migration `2026_05_12_000003` aveva migrato a JSON `responsabile_ids`)
- **Soluzione**:
1. Rimossa relazione `responsabili()` da `Gruppo.php`
2. Rimossa eager loading `responsabili` da `GruppoController@index()`
3. Mantenuti metodi helper `getResponsabiliIds()` e `getResponsabili()` che leggono dal campo JSON
4. Corretti riferimenti in `gruppi/show.blade.php` e `individui/show.blade.php` per usare `getResponsabili()`
- **Impostazioni view**: Nuovo tab `#tipologie-eventi` con tabella sortabile, modali add/edit, stessi pattern esistenti
- **Sidebar**: Nuovo link "Tipologie Eventi" sotto Impostazioni (solo superadmin)
- **Evento model**: Aggiunta relazione `tipologiaEvento()` (BelongsTo via 'nome')
- **EventoController**: Validazione cambiata da `nullable|string|max:100` a `nullable|exists:tipologie_eventi,nome` in store() e update(); create/edit passano `$tipologieEventi` alla view
- **Views eventi**:
- `create.blade.php`: Dropdown "Tipo Evento" dopo Descrizione Breve
- `edit.blade.php`: Text libero → dropdown; fallback per vecchi valori non presenti
- `show.blade.php`: Badge "Tipologia" nella sezione Data e Orario; colonna rinominata "Ricorrenza"
- `index.blade.php`: Nuova colonna sortabile "Tipologia" con badge; colonna "Tipo" → "Ricorrenza"; colspan 8→9
### 26 Maggio 2026 - ACL Fixes & Form Modules Sync
- **Problema**: Form admin utenti (create/edit) aveva lista moduli hardcoded e mancante di `'report'`; user 4 (fbarachino) non aveva permesso `report` né `is_admin`
- **Fix form edit/create**: Sostituito `@foreach(['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste'] as $module)` con `@foreach(\App\Models\User::MODULES as $module)` in entrambe le view (`edit.blade.php:76`, `create.blade.php:66`) — ora la lista è sempre allineata alla costante `User::MODULES`
- **Fix user 4**:
- Aggiunto `'report' => 2` alle permissions (full access)
- Itera tutti gli utenti (inclusi superadmin) e aggiunge con default 0 ogni modulo mancante in `User::MODULES`
- Previene discrepanze future tra permissions salvate e MODULES costante
- **Files modificati**:
- `resources/views/admin/utenti/edit.blade.php`: linea 76, hardcoded → `User::MODULES`
- `resources/views/admin/utenti/create.blade.php`: linea 66, hardcoded → `User::MODULES`
- **File creato**:
- `app/Console/Commands/SyncUserPermissions.php`: nuovo comando artisan
- **Stato sidebar user 4**: `isSuperAdmin()` = true → tutti i menu visibili, `canAccess('report')` = true
### 26 Maggio 2026 - ACL 'settings' Module (Impostazioni/Admin Permission)
- **Problema**: Non esisteva un permesso ACL specifico per accedere al menu Impostazioni e Admin. L'accesso era legato esclusivamente a `isSuperAdmin()` bypass totale. Inoltre l'`ImpostazioniController` usava `authorizeWrite('viste')` come piggyback scorretto.
- **Soluzione**: Nuovo modulo `settings` separato, slegato da `viste` e da `isSuperAdmin()`.
- **Modifiche**:
- `User.php`: aggiunto `'settings'` a `MODULES` (costante linea 36)
- `adminlte.blade.php` sidebar: `@if(Auth::user()->isSuperAdmin())` → `@if(Auth::user()->canManage('settings'))` per menu Impostazioni e Admin (linee 205-226)
- `AdminOnly.php` middleware: `isSuperAdmin()` → `isSuperAdmin() || canManage('settings')` — permette accesso a `/admin/*` anche a non-superadmin con permesso settings:2
- `ImpostazioniController.php`: rimpiazzate tutte le 16 occorrenze di `authorizeWrite('viste')` e `authorizeDelete('viste')` con `'settings'`
- `EmailSettingsController.php`: aggiunto `$this->authorizeWrite('settings')` in tutti i 5 metodi pubblici (index, save, testConnection, testSmtp, syncNow) — prima non avevano alcuna autorizzazione esplicita
- `SyncUserPermissions.php` aggiornato: ora include anche superadmin nella sync (per consistenza dati)
- Eseguito `php artisan users:sync-permissions` → user 1 e 4 aggiornati con `settings: 0`
- **Comportamento risultante**:
- `isSuperAdmin()` bypassa sempre tutto (base Controller.php)
- Utente normale con `settings:2` (Completo) → vede menu Impostazioni+Admin, può operare
- Utente normale con `settings:0` → menu nascosto, 403 su accesso diretto URL
- `settings:1` (Lettura) non basta per operazioni di scrittura (sidebar non mostra il menu perché richiede `canManage`)
- `app/Console/Commands/SyncUserPermissions.php`: superadmin inclusi nella sync
### 26 Maggio 2026 - Fix Salvataggio is_admin (Checkbox Superadmin)
- **Problema**: Non era possibile togliere il flag "Superadmin (bypass ACL)" dall'interfaccia admin. Il flag rimaneva sempre true dopo il salvataggio.
- **Cause** (3 bug concatenati):
1. **`url()` assoluto** nell'action del form edit (`url('/admin/utenti/' . $user->id)`) → da accesso remoto generava `http://localhost/admin/utenti/4`, il POST falliva silenziosamente
2. **Nessun hidden field** per unchecked checkbox → quando la checkbox veniva deselezionata, il campo `is_admin` non veniva inviato nella richiesta. Anche se il controller aveva `?? false`, il combinato col bug #1 impediva il salvataggio
3. **Mancanza `old()`** per lo stato checkbox → su validation error redirect, la checkbox mostrava il valore DB invece del valore inviato
- **Fix applicati**:
- `edit.blade.php`: `url(...)` → path relativo `/admin/utenti/...` (linea 20); aggiunto hidden `<input type="hidden" name="is_admin" value="0">` prima della checkbox; `old()` per stato checkbox e status/role_preset_id
- `create.blade.php`: aggiunto hidden field come sopra; default per `settings` = `'0'` (Nessuno) invece di `'1'` (Lettura) per nuovi utenti
- `UtenteController.php` (store + update): rimosso `?? false` da `$user->is_admin` — ora usa direttamente `$validated['is_admin']` (sempre presente grazie all'hidden field)
- `AuthController.php`: aggiunti `'report' => 0` e `'settings' => 0` ai default permissions per nuove registrazioni (sia first-user che non)
- **Files modificati**:
- `resources/views/admin/utenti/edit.blade.php`: form action, hidden field, old()
- `buildSymfonyEmail(to, subject, body)` — costruisce email con From, Reply-To
- `sendEmail(to, subject, body, attachmentPaths)` — invia via Symfony Mailer
- `sendReport(subject, body)` — invia report riepilogo a `verify_email`
- **Logica Reply-To**: se impostato `reply_to` (es. `no-reply@parrocchia.it`) → quello; fallback `verify_email`; nessun Reply-To se entrambi null
- **Report invio**: dopo ogni invio massivo via sender, una email di riepilogo viene inviata a `verify_email` con: mittente, oggetto, totale, ok, falliti, dettaglio errori
-`show.blade.php`: pulsante "Esporta ICS" nella toolbar azioni
-`index.blade.php`: pulsante "Esporta ICS" nella card-tools; se ci sono righe selezionate esporta solo quelle, altrimenti esporta tutti i risultati della ricerca corrente
-`calendar.blade.php`: pulsante "Esporta ICS" che esporta tutti gli eventi
### 27 Maggio 2026 - Mittenti Aggiuntivi in Sezione Email di /impostazioni
- **Modifica**: Spostata gestione sender accounts (Mittenti Aggiuntivi) da pagina dedicata `/impostazioni/email` a **sottosezione della sezione Email** nella pagina principale `/impostazioni`
- **Sub-tab aggiunto**: pill "Mittenti Aggiuntivi" dopo Firma nei sottotab della sezione Email
- **Tab-pane `email-mittenti`**: tabella sender con colonne Email, Nome, SMTP, Reply-To, Verify, Stato, Azioni; stessi campi CRUD della vista dedicata
- **Form puliti**: pulsanti Elimina usano fetch AJAX (`deleteSender()`) invece di form annidati per evitare HTML invalido (nested forms dentro il form principale Email)
- **Controller**: `ImpostazioniController@index` ora passa `$senderAccounts` alla view
- **`senderDestroy()`** in `EmailSettingsController`: ora ritorna JSON per richieste AJAX (`expectsJson()`)
- **Sidebar**: rimossa voce "Impostazioni Email" aggiunta in precedenza sotto Email (non serve — le impostazioni email sono accessibili dalla sezione Email in `/impostazioni`)
(Last updated: 28 Maggio 2026 - AJAX browser, folder actions, Google Drive fix)
### 28 Maggio 2026 - AJAX Browser, Folder Actions, Google Drive + Bug Fixes
- **Download/Preview proxy**: Aggiunti `downloadRemote()` e `previewRemote()` in StorageRepositoryController — stream di file remoti per download e anteprima inline
- **AJAX browser**: Repository nel folder tree usano `browseRepo(repoId, nome, path)` via AJAX al `browse` endpoint — mostra cartelle e file remoti in grid/list view (stesso formato del file manager locale)
- **Funzioni JS**: `browseRepo()`, `loadRemoteContents()`, `renderRemoteContents()` (grid/list rendering), `downloadRepoFile()`, `previewRepoFile()`, `getFileIcon()` (MIME-type mapping), `showLocalContent()` — navigazione remota senza ricarica pagina
- **View toggle remoto**: `setView()` ora ricarica i contenuti remoti in grid/list se `currentRepoBrowse` è attivo
- **Folder tree rename**: Pulsante penna su hover → SweetAlert2 prompt → `PUT /documenti/cartelle/{id}` con `{nome}`
- **Folder tree delete**: Pulsante X rossa su hover → SweetAlert2 confirm → `DELETE /documenti/cartelle/{id}` (con warning se documenti/sottocartelle presenti nel controller)
- **Folder move**: `DocumentoCartellaController@update` ora accetta `parent_id` (con validazione anti-ciclo) — preparato per drag-and-drop futuro
- **CSS**: `.folder-actions` visibili su hover del `.list-group-item`
- **Google Drive fix**: Aggiunto `normalizePath()` in `StorageRepositoryService` — converte `/` → `''` per compatibilità cross-adapter (Google Drive non accetta `/` come root path). Usato in `testConnection()` e `listContents()`
- **testRepo() fix**: Passato `this` esplicitamente invece di `event.target` (che puntava all'`<i>` interno causando icona duplicata e disabilitazione errata del bottone)
- **Stato**: ✅ 18/19 test pass (solo ExampleTest pre-esistente). Sintassi PHP OK, view cache OK.
- **Rimosso registration**: Eliminata view `auth/register.blade.php`, rimossi metodi register da AuthController; rimosso link "Non hai un account? Registrati" dalla login
- **Aggiunto password reset**:
- Routes password.request, password.email, password.reset, password.update in routes/web.php
- Metodi showForgotForm(), sendResetLink(), showResetForm(), reset() in AuthController
-`User` model: implementato CanResetPassword con sendPasswordResetNotification() custom via Symfony Mailer su EmailSetting SMTP
- **Login fix**: Checkbox "Ricordami" ora usa `$request->has('remember')` invece di hardcoded false
- **Login update**: Aggiunto link "Password dimenticata?" che punta a `route('password.request')`
- **ProfileController**: Creato con `show()` (view auth.profile) e `updatePassword()` con validazione `current_password` e `Password::min(8)`
- **Layout fix**: Link "Profilo" in adminlte.blade.php ora punta a `route('profile')` invece di `#`
- **Profile view**: Già esistente `auth/profile.blade.php` con form cambio password funzionante
### 27 Maggio 2026 - Report Enhancements
- **ReportColumnRegistry** (`app/Helpers/ReportColumnRegistry.php`): Registry centralizzato di colonne disponibili per entity type (individui, gruppi, eventi, documenti, contatti) con supporto per colonne dirette, accessor e relazioni (dot notation tipo `contatti.email`, `gruppi.nome`). Metodo `resolveRelationValue()` per risolvere valori su relazioni nidificate.
- **ReportController fix**:
-`storeCustom()`: colonne salvate correttamente come array in config['columns']; supporto per array da Select2 o comma-separated
-`runCustomReport()`: eager loading automatico per relazioni in colonne dot notation; risoluzione valori via `ReportColumnRegistry::resolveRelationValue()`
-`exportCSV()`: supporto per custom_id con filename da slug del nome report; BOM UTF-8 per Excel
-`resolveReport()`: metodo centralizzato per match report type + tipologia filter
-`run()`: supporto per custom_id per eseguire report personalizzati dalla stessa route
- **Model `Documento`**: aggiunto `cartella_id` in `$fillable`, nuova relazione `cartella()` (BelongsTo → DocumentoCartella). Tutte le relazioni esistenti (eventi/gruppi/individui/target/user/tenant) INTATTE
- **Mass Move**: Nuovo pulsante "Sposta selezionati in..." nella toolbar azioni massive, modal con stesso dropdown cartelle del move singolo, `massMove()` controller aggiorna `cartella_id` su `Documento::whereIn('id', $ids)`, JS handler submit form `/documenti/mass-move` con redirect/reload. Pulsante "+ Nuova cartella" nella modale con SweetAlert2 prompt e AJAX al backend
- **Cartelle miste a documenti (Opzione A)**: Controller `index()` passa `$sottoCartelle` (figli della cartella corrente, o root se nessuna filtrata) alla vista; griglia mostra folder card (fas fa-folder, card-warning) prima delle document card; lista mostra righe folder (tr class=table-warning) con colonne Nome/Cartella/-/-/-/Apri prima delle righe documento; empty state "Nessun elemento presente" se entrambi vuoti
- **Fix nested forms**: Aggiunto `</form>` di chiusura per `massForm` (linea 391) prima dei modali. Il form `massForm` avvolgeva tutto il contenuto incluse le modali, causando `<form>` nidificati (HTML invalido) — browser ignorava i form interni come `newFolderForm`, impedendo la creazione cartelle dal "+".
-`resources/views/impostazioni/index.blade.php`: nuova sezione Repository Documenti
-`routes/web.php`: 4 nuove rotte cartelle + import DocumentoCartellaController
(Last updated: 28 Maggio 2026 - Bug fix: JS syntax error, storage disk persistence)
### 28 Maggio 2026 - Bug Fix: JS syntax error, storage disk persistence
- **Problema 1 — Vista lista non funzionante**: `setView()` in `documenti/index.blade.php` aveva una `}` extra alla riga 1183 che causava `SyntaxError` JS. L'intero blocco `<script>` (1507 righe) non veniva parsato, rendendo inutilizzabili: toggle griglia/lista, renameFolder, deleteFolder, AJAX browser remoto, modali AJAX.
- **Fix**: Rimossa la `}` extra alla riga 1183.
- **Problema 2 — Icone cartelle assenti/non funzionanti**: Le icone rename/delete sono nel partial `_folder_tree.blade.php` con CSS hover (`.list-group-item:hover .folder-actions { display: inline-flex !important; }`). Le icone erano renderizzate nel DOM ma il JS era rotto: `renameFolder()` e `deleteFolder()` mai definite.
- **Fix**: Il fix del JS (rimozione `}` extra) risolve anche questo problema.
- **Problema 3 — Impostazione percorso locale non funzionante**: `AppSetting::getDocumentiStorageDisk()` veniva risolto dinamicamente sia in store che in download/preview/destroy. Cambiando il disco nelle impostazioni, i documenti caricati in precedenza non erano più accessibili (es. `local`→`public` causava 404 sui vecchi file).
- **Fix**: Creata migration `2026_05_28_000001_add_storage_disk_to_documenti.php` che aggiunge colonna `storage_disk` (nullable string) alla tabella `documenti`.
-`DocumentoController@store()` ora salva `storage_disk` sul record del documento.
-`resolveStorageDisk($documento)` ora restituisce il disco persistito se disponibile, altrimenti il default da AppSetting.
- Tutti i metodi (download, preview, destroy, massDestroy) usano `resolveStorageDisk($documento)` e beneficiano automaticamente del fix.
- **Stato**: ✅ 18/19 test pass (solo ExampleTest pre-esistente). `php -l` su tutti i file modificati OK. `php artisan view:cache` OK.
- **Icone cartelle sempre visibili**: Rimosso `style="display:none;"` da `.folder-actions` in `_folder_tree.blade.php` — rename, move e delete sono ora sempre visibili, non solo su hover. CSS aggiornato: `.list-group-item .folder-actions { display: inline-flex; }`.
- **Aggiunto pulsante "Sposta" per cartelle**: Nuovo bottone `fa-folder-open` nel folder tree → funzione `moveFolder(id, nome)` con SweetAlert2 prompt contenente `<select>` con gerarchia cartelle (esclude se stessa e figli) → `PUT /documenti/cartelle/{id}` con `parent_id`. Aggiunti dati `folders` JSON in linea.
- **Aggiunto pulsante "Associa" per file singoli**: Bottone `fa-link` in ogni card (grid) e riga (list) → `showAssociateModal(id)` pre-popola il modale `#associateModal` con l'ID del documento singolo.
- **UpdateModal con target dinamico**: Aggiunto `#updateTargetGroup` nel modal "Cambia tipo/contesto": quando si seleziona `individuo`/`gruppo`/`evento`/`mailing`, compare dropdown con i dati caricati dagli array JS esistenti (`individui`, `gruppi`, `eventi`, `mailingLists`). Funzione `toggleUpdateTarget(value)` show/hide + popolamento.
- **massUpdate controller esteso**: `massUpdate()` in `DocumentoController` ora accetta `visibilita_target_id` e `visibilita_target_type` come parametri opzionali. Se presenti, aggiorna contesto + target sul DB con `$typeMap`.
- **Fix saveAppSettings null error**: `ImpostazioniController@saveAppSettings` ora preserva i valori esistenti quando i campi arrivano `null` (dovuto a `ConvertEmptyStringsToNull` middleware): `$settings->nome_applicazione = $validated[...] ?? $settings->nome_applicazione ?? ''`. Applicato a tutti i campi stringa. Risolve `SQLSTATE[23000]: Column 'nome_applicazione' cannot be null`.
- In caso di file non accessibili, li salta e include solo quelli disponibili
- **Files modificati**:
-`routes/web.php`: nuova route `POST /documenti/mass-download`
-`app/Http/Controllers/DocumentoController.php`: nuovo metodo `massDownload()`
-`resources/views/documenti/index.blade.php`: icone cartelle in grid+list, bottone "Scarica" mass action, funzione JS `massDownload()`
- **Stato**: ✅ 18/19 test pass (solo ExampleTest pre-esistente). `php -l` OK. `php artisan view:cache` OK.
### 28 Maggio 2026 - Fix pagina bloccata: massaAction() mancante di chiusura }
- **Problema**: La funzione `massAction()` nel JS di `documenti/index.blade.php` aveva un `}` di chiusura mancante, causando:
- Tutto il JS successivo (tutte le funzioni) veniva interpretato come annidato dentro massAction
- Le funzioni non erano accessibili globalmente → icone azioni click non funzionanti (renameFolder, deleteFolder, showUpdateModal, ecc.)
- La pagina risultava bloccata/non reattiva
- **Causa**: Introdotto nel refactoring file manager (27 Maggio), il `}` finale di `function massAction()` era stato perso. Il codice originale in git HEAD aveva 3 `}` di chiusura (catch + if + function), il file corrente ne aveva solo 2.
- **Fix**: Aggiunto `}` mancante per chiudere `massAction()`.
- **Verifica**: 182 `{` = 182 `}` nel blocco JS. 18/19 test pass. `php -l` OK. `php artisan view:cache` OK.
### 28 Maggio 2026 - Fix: delete redirect, folder azioni non funzionanti, escaping JS
- **Fix delete redirect dashboard**: `DocumentoController@destroy()` usava `url('/')` come default redirect → dopo eliminazione si finiva in dashboard. Cambiato a `url()->previous()`.
- **Fix folder azioni non funzionanti**: Tutti gli onclick handler per renameFolder/moveFolder/deleteFolder usavano `addslashes()` che non gestisce correttamente apici nei nomi in contesto HTML → JS error silenzioso, funzioni non eseguite. Sostituito con `json_encode()` che produce stringhe JS correttamente escaped con virgolette.
- **Fix browseRepo repo names**: Stesso fix `addslashes` → `json_encode` per i nomi repository negli onclick.
### 28 Maggio 2026 - Fix Google Drive Repository: credenziali mancanti + wipe su update
- **Problema**: Drive-Segreteria (repo #1) mostrava 0 file nonostante l'account Google Drive avesse 2 cartelle e 1 file. Il test connessione diceva "Connessione riuscita. 0 elementi trovati nella root." — falso positivo.
- **Cause** (2 bug):
1.**Credenziali OAuth mancanti**: `client_secret` e `refresh_token` erano entrambi `null` nel DB. La Google API falliva silenziosamente dentro `fetchDirectory()` (catch vuoto → 0 risultati).
2.**Update wipe credenziali**: `StorageRepositoryController@update()` chiamava `encryptSensitiveConfig()` che criptava solo campi NON vuoti. Quando il form edit veniva salvato con `client_secret=''` e `refresh_token=''` (placeholder), i vecchi valori criptati venivano sovrascritti con stringhe vuote.
- **Fix**:
1.`StorageRepositoryService::encryptSensitiveConfig()`: aggiunto parametro `$existingConfig`. Se un campo sensibile è vuoto nel nuovo config ma presente in `$existingConfig`, lo preserva.
2.`StorageRepositoryController@update()`: passa `$storageRepository->config` come existing config a `encryptSensitiveConfig()`.
3.`StorageRepositoryService::buildGoogleDrive()`: validazione esplicita — lancia `InvalidArgumentException` se mancano `client_id`, `client_secret` o `refresh_token`.
4.`testConnection()` e `listContents()`: check preventivo credenziali Google Drive complete prima di chiamare `buildFilesystem()`, con messaggio chiaro.
- **Per risolvere**: l'utente deve reinserire `client_secret` e `refresh_token` nella modale di modifica del repository (Impostazioni → Repository Remoti → Drive-Segreteria → Modifica). Dopo il fix, questi campi non verranno più persi al prossimo salvataggio.
- **Stato**: ✅ 18/19 test pass (solo ExampleTest pre-esistente). `php -l` OK. `php artisan view:cache` OK.
### 28 Maggio 2026 - OAuth Google Drive integrato nell'app
- **Implementazione**: Flusso OAuth 2.0 completo senza passare dall'OAuth Playground
- **Route nuove**:
-`GET /auth/google-drive/redirect` → reindirizza a Google OAuth
-`GET /auth/google-drive/callback` → riceve il token, lo salva nel repository
- **Controller**: `StorageRepositoryController@oauthRedirect()` e `oauthCallback()`
- **Flusso UX**:
1. Utente seleziona Google Drive → compila Client ID, Client Secret, Nome
2. Click "Autorizza con Google" → reindirizzato a Google
3. Login con account Google e autorizzazione
4. Callback salva automaticamente refresh_token + campi form nella sessione
5. Redirect a `/impostazioni#repository` → modale si riapre con tutti i campi preservati
- **Fix preservazione campi**: `oauthRedirect()` salva in sessione anche `nome`, `is_active`, `root_folder_id`. Il callback li restituisce nel flash `google_drive_new_token`. JS ripristina tutti i campi nel modal (non chiama più `resetRepoForm()` che svuotava tutto).
-`app/Http/Controllers/ImpostazioniController.php`: passa `$googleDriveNewToken` alla view
-`resources/views/impostazioni/index.blade.php`: pulsante + status + JS auto-ripristino campi
- **Config necessaria una tantum**: aggiungere `{APP_URL}/auth/google-drive/callback` come Authorized Redirect URI in Google Cloud Console
- **Stato**: ✅ 18/19 test pass. `php -l` OK. `php artisan view:cache` OK. JS braces 176/176.
### 28 Maggio 2026 - Fix Google Drive: normalizePath() root path
- **Problema**: Google Drive non visualizzava file e cartelle (repository remoto mostrava 0 elementi).
- **Causa**: `normalizePath('/')` in `StorageRepositoryService.php` restituiva `'/'` invece di `''`. L'adapter `as247/flysystem-google-drive` non accetta `'/'` come root path, causando fallimento silenzioso di `listContents()` → `[]`.
- **Contraddizione**: MEMORY.md del 28 Maggio documentava `normalizePath()` come "converte `/` → `''`" ma l'implementazione effettiva a riga 122 faceva `return '/'`.
- **Fix**: Cambiato `return '/'` in `return ''` in `normalizePath()`.
- **Stato**: ✅ 10/10 StorageRepository test pass. `php -l` OK.
### 28 Maggio 2026 - Fix browseRepo ReferenceError: getRepoIcon inesistente
- **Problema**: Cliccando "Drive-Segreteria" nella sidebar documenti non succedeva nulla (contenuto remoto non caricato).
- **Causa**: `updateRemoteBreadcrumb()` chiamava `getRepoIcon(repoIconFromId(...))` ma `getRepoIcon()` non è mai stata definita. Il `ReferenceError` bloccava l'esecuzione di `browseRepo()` prima che `loadRemoteContents()` venisse chiamato — nessuna fetch AJAX, nessun contenuto caricato.
- **Fix**: Rimossa chiamata a `getRepoIcon()`, `repoIconFromId()` già restituisce il `className` completo (es. `"fab fa-google-drive text-danger"`). Rimosso anche `fab` hardcoded dal template (duplicato).
- **Files modificati**:
-`resources/views/documenti/index.blade.php`: riga 1383, `getRepoIcon(...)` → diretto
- **Stato**: ✅ JS braces 206/206 OK. `php -l` OK. `php artisan view:cache` OK.
### 28 Maggio 2026 - CAUSA RADICE: SweetAlert2 mancante in layout
- **Problema**: Tutte le icone azioni cartelle (Rinomina, Sposta, Elimina) non funzionavano — click senza effetto. Anche il pulsante "+ Nuova cartella" nel modal upload non funzionava.
- **Causa**: SweetAlert2 (`Swal`) **non era incluso nel layout**`adminlte.blade.php`. Le funzioni `renameFolder()`, `moveFolder()`, `deleteFolder()` e il listener `massMoveNewFolderBtn` chiamano `Swal.fire()`, che lanciava `ReferenceError: Swal is not defined`.
- **SweetAlert2 assente**: Non presente né via CDN, né via NPM (`package.json` non ha sweetalert2), né via Vite. L'unica dipendenza JS era jQuery + Bootstrap + AdminLTE via CDN.
- **Perché i fix precedenti sembravano non funzionare**: Anche dopo le correzioni a `getRepoIcon()` e `normalizePath()`, la view cache conteneva ancora il vecchio codice JS. SweetAlert2 mancante bloccava indipendentemente le azioni cartelle.
- **Fix**: Aggiunto CDN SweetAlert2 11 nel layout `adminlte.blade.php` subito dopo AdminLTE JS, prima di `@yield('scripts')`.
(Last updated: 01 Giugno 2026 - Sistema Backup Completo + Help Header + Script Migrazione)
### 01 Giugno 2026 - Sistema Backup Completo per Migrazione
- **Header navbar**: Aggiunta icona `fa-question-circle` che linka a `route('help')` — posizionata nella navbar superiore prima della campanella notifiche
- **Sidebar**: Aggiunto link "Backup" sotto Admin (protetto da `canManage('settings')`)
- **Sicurezza**: self-destruct dopo installazione, blocco re-install se .env esiste, CSRF token, HTTPS warning, password mai esposte in HTML dopo invio
- **Migration backup**: `2026_06_01_100155_add_backup_settings_to_app_settings` estesa con `backup_auto_enabled`, `backup_auto_frequency`, `backup_auto_hour`
- **.env.example**: aggiornato — default MySQL invece di SQLite, locale it, righe minime essenziali
- **Guida Installazione**: nuovo tab nella help page con:
-`resources/views/help/index.blade.php`: nuovo tab Installazione
- **Stato**: ✅ PHP syntax OK su tutti i file. ✅ view:cache OK. ✅ 7 route backup registrate. ✅ `php artisan backup:run` registrato. ✅ installer/ struttura completa.