Files
glastree/MEMORY.md
T

70 KiB

MEMORY.md - Glastree Project

Project Context

Laravel 13 / PHP 8.4 project for parish management (Glastree). Full codebase exists and is operational.


Issues Open

  1. Drive-Segreteria-ADIM (#3): Root ha 0 elementi. Verificare root_folder_id nelle impostazioni repository.
  2. Nextcloud_MB (#4): Connessione "Unauthorized" dopo fix authType — credenziali da verificare.

System Status

FUNZIONANTE (Stable)

  • Dashboard
  • Individui (CRUD completo) - funziona da internet (con viste personalizzate per colonna)
  • Gruppi (CRUD completo) - funziona da internet (con vista tabella/albero e colonne personalizzate)
  • Documenti - funziona da internet
  • Eventi (CRUD completo) - funziona da internet (con mass delete)
  • Calendario eventi - funziona da internet
  • Email (sistema completo)
  • Impostazioni (App Settings, Logo, Email Settings)
  • Mailing List - funziona da internet (con mass delete)
  • Report (predefiniti e personalizzati, accesso tramite permesso 'report')

⚠️ IN COMPLETAMENTO

  • Sistema Email: funziona al 95%, SMTP potrebbe necessitare credenziali corrette
  • Nuova implementazione: email sync logging

Progress Summary

Eventi Enhancements (Completed)

  • Migration 2026_05_12_142009_add_location_to_eventi_table added luogo_indirizzo and luogo_url_maps columns
  • Evento model updated with fillable fields
  • EventoController updated: index() with search + sorting, store/update with location fields
  • eventi/index.blade.php: search input, sortable columns (nome_evento, created_at), delete button
  • 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()
  • Routes: POST /impostazioni/logo, POST /impostazioni/logo/remove
  • Views: Sezione Logo in /impostazioni per caricare logo grande e piccolo
  • Login page: Logo dinamico da AppSetting::getLogoUrl()
  • Sidebar: Logo piccolo da AppSetting::getLogoSmallUrl() con fallback a icona

16 Maggio 2026 - Email Signature Feature

  • Migration: 2026_05_16_000003_add_signature_to_email_settings - aggiunge colonna signature (longText)
  • Migration: 2026_05_16_000004_add_signature_enabled_to_email_settings - aggiunge colonna signature_enabled (boolean, default true)
  • Model: EmailSetting aggiornato con signature e signature_enabled in fillable, cast boolean, metodo getSignature()
  • View: admin/email-settings/index.blade.php aggiornato con:
    • Titolo cambiato da "Email IMAP" a "Email Settings"
    • Nuova sezione "Firma" nel menu laterale
    • Toggle switch "Attiva firma automatica" per abilitare/disabilitare la firma
    • Editor WYSIWYG Quill.js per la composizione della firma
    • Caricamento lazy di Quill.js da CDN
  • Controller: EmailController aggiornato con metodo appendSignature(body, signature, enabled) che aggiunge la firma al body solo se abilitata
  • La firma viene aggiunta sia alle email inviate via SMTP che a quelle salvate in "Invio"
  • Sidebar: Voce di menu cambiata da "Email IMAP" a "Impostazione Email"

Sistema Email - Dettagli Tecnici

Routes Eventi

GET  /eventi              → eventi.index (elenco)
GET  /eventi/calendar     → eventi.calendar (calendario)
GET  /eventi/calendar/events → eventi.calendar.events (eventi per FullCalendar)
GET  /eventi/create       → eventi.create
POST /eventi              → eventi.store
GET  /eventi/{id}         → eventi.show
GET  /eventi/{id}/edit   → eventi.edit
PUT  /eventi/{id}         → eventi.update
DELETE /eventi/{id}       → eventi.destroy

Routes Email

POST   /email/send              → email.send
POST   /email/sync/quick        → email.sync.quick
POST   /email/bulk              → email.bulk
GET    /email/{folder?}         → email.index
POST   /email/{id}/star         → email.star
DELETE /email/{id}              → email.destroy

Folder Types

  • inbox, sent, drafts, archive, starred, trash

SMTP Configuration

  • Credenziali salvate in email_settings con criptatura
  • Password decriptata con getDecryptedPassword() o getDecryptedSmtpPassword()
  • Display name validato con filter_var($fromName, FILTER_VALIDATE_EMAIL) - se è un'email usa "Glastree"

Email Signature

  • Campo signature in email_settings (longText, nullable)
  • WYSIWYG editor Quill.js via CDN
  • Metodo appendSignature() in EmailController aggiunge "---\n" + signature al body
  • Applicato sia in sendViaImap() che in storeSentMessage()

Key Decisions

Database

  • Location uses separate columns: luogo_indirizzo (varchar 500) and luogo_url_maps (text)
  • Map preview via Google Maps embed iframe using the address
  • Sortable columns: nome_evento, created_at, descrizione_evento, tipo_recorrenza, gruppi, responsabili
  • Multi-select uses Ctrl+click pattern with array input names

Email

  • SMTP defaults to Gmail if not configured
  • Email name sanitized to comply with RFC 2822
  • Sincronizzazione IMAP limitata a 100 messaggi per cartella
  • Signature appended with separator "---\n" before the signature text

UI

  • AdminLTE per tutte le viste
  • Sidebar con menu navigazione
  • Compose email senza sidebar sinistra

Relevant Files

Core Controllers

  • app/Http/Controllers/EventoController.php: eventi with extended sorting
  • app/Http/Controllers/EmailController.php: email system completo, signature auto-append
  • app/Http/Controllers/Admin/EmailSettingsController.php: email settings + test SMTP

Models

  • app/Models/EmailSetting.php: getDecryptedPassword(), getDecryptedSmtpPassword(), getSignature()
  • app/Models/Evento.php: fillable with location fields
  • app/Models/Gruppo.php: getResponsabiliIds(), getResponsabili()

Views

  • resources/views/admin/email-settings/index.blade.php: Email settings with signature section (Quill.js WYSIWYG)
  • resources/views/eventi/index.blade.php: sortable columns
  • resources/views/eventi/edit.blade.php: fixed nested form, added tipo_evento and note
  • resources/views/eventi/show.blade.php: removed duplicate "Dove", added "Luogo" section
  • resources/views/email/index.blade.php: mailbox view con AdminLTE
  • resources/views/email/compose.blade.php: compose senza sidebar sinistra
  • resources/views/layouts/adminlte.blade.php: sidebar menu Email con ordine corretto

Migrations

  • 2026_05_16_000003_add_signature_to_email_settings.php: Adds signature column to email_settings table

Commands Utili

# Clear all caches
php artisan optimize:clear

# Recompile views
php artisan view:cache

# Check routes
php artisan route:list --name=email

Known Issues / TODO

  1. SMTP authentication con Infomaniak - verificare credenziali corrette
  2. Invio email richiede password SMTP corretta nel form impostazioni

16 Maggio 2026 - URL Hardcoded Fix (Internet Access)

  • Problema: Da internet (accesso remoto) molti form e link puntavano a http://localhost invece del server remoto
  • Causa: Uso di url('/path') in tutte le view che generava URL basati su APP_URL=http://localhost
  • Soluzione: Sostituzione di tutte le occorrenze di url('/path') con path relativi /path
  • Files modificati:
    • individui/edit.blade.php: form action, link gruppi/documenti, fetch API calls
    • individui/show.blade.php: link gruppi, documenti, form, fetch API calls
    • gruppi/edit.blade.php: form action, link individui/documenti, fetch API calls
    • gruppi/show.blade.php: link individui, eventi
    • eventi/edit.blade.php: form action, link annulla, documenti
    • eventi/show.blade.php: breadcrumbs, link gruppi/responsabili, documenti
    • eventi/create.blade.php: form action, link annulla
    • eventi/index.blade.php: link create, reset, eventi, gruppi
    • documenti/index.blade.php: link targets, edit, delete, mass actions, fetch API
    • documenti/edit.blade.php: form action, download, back link
    • mailing-liste/edit.blade.php: fetch API calls
    • eventi/calendar.blade.php: eventSource URL
  • Layout fix: Aggiunto meta tag csrf-token in adminlte.blade.php head

16 Maggio 2026 - Individui Edit Form Fix

  • Problema: Form non salvava da accesso internet, il pulsante submit non funzionava
  • Causa: Il button type="submit" non triggherava il form submission
  • Soluzione:
    • Cambiato button da type="submit" a type="button" con onclick="document.getElementById('main-form').submit()"
    • Path relativi per tutti i form action
    • CSRF token in meta tag del layout

17 Maggio 2026 - Bulk Action Error Fix

  • Problema: Azioni massive sulle email (sposta nel cestino) mostravano popup "errore" generico
  • Causa: authorizeWrite() lancia eccezione 403 HTML invece di JSON per richieste AJAX
  • Soluzione: Aggiunto try-catch in bulkAction(), markRead() e toggleStar() per ritornare JSON con messaggio appropriato

17 Maggio 2026 - Nginx Proxy Manager Config

  • Problema: Email non funziona da internet (dietro NPM)
  • bootstrap/app.php: Aggiunto $middleware->trustProxies(at: '*')
  • Configurazione .env:
    APP_URL=
    APP_PROTOCOL=https
    FORCE_HTTPS=true
    TRUSTED_PROXIES=*
    
  • Middleware rimosso: ForceHttps non necessario - NPM gestisce terminazione HTTPS
  • Advanced Tab NPM: Abilitare X-Forwarded-Proto e headers proxy

17 Maggio 2026 - Form Annidati Fix

  • Problema: Contatti, gruppi e documenti in individui/edit non venivano salvati
  • Causa: Sezioni fuori dal main-form (chiusura prematura)
  • Soluzione: Spostata chiusura form dopo tutte le sezioni, aggiunto pulsante "Salva Tutte le Modifiche"
  • Contatti: Aggiunto pulsante salva nella riga nuova (icona💾)

17 Maggio 2026 - Bulk Action Email Fix

  • Problema: Azioni massive su email mostravano popup "errore"
  • Causa: authorizeWrite() lancia eccezione 403 HTML invece di JSON
  • Soluzione: try-catch in bulkAction(), markRead(), toggleStar()

(Last updated: 25 Maggio 2026 - Fix export CSV individui)

18 Maggio 2026 - AGENTS.md Update

  • Aggiunta regola "Verifica Salvataggio" nelle Regole Operative AI
  • Obbligo di verificare SEMPRE l'avvenuta riuscita dopo operazioni di modifica (store, update, delete, upload, sync)

25 Maggio 2026 - IndividuoController::elimina() Method

  • Problema: Errore 500 "Call to undefined method App\Http\Controllers\IndividuoController::elimina()" su richiesta POST /individui/{id}/elimina
  • Causa: Route definita in routes/web.php:58 ma metodo elimina() non implementato nel controller
  • Soluzione: Aggiunto metodo elimina() in IndividuoController che:
    • Verifica autorizzazione con authorizeDelete('individui')
    • Riceve parametri elimina_contatti e elimina_documenti dal form
    • Elimina opzionalmente i contatti collegati se richiesto
    • Elimina opzionalmente i documenti collegati (con rimozione file fisico) se richiesto
    • Elimina l'individuo e redirect alla lista con messaggio di successo
  • Files modificati:
    • app/Http/Controllers/IndividuoController.php: aggiunto metodo elimina() dopo destroy()

25 Maggio 2026 - Fix Eliminazione Singola e Massiva

  • Problema: L'eliminazione non veniva eseguita né per il singolo né per la mass action
  • Cause:
    1. $request->boolean() interpretava male i valori 'true'/'false' come stringhe
    2. deleteSelected() faceva submit multipli di form separati causando redirect concorrenti
  • Soluzione:
    1. Sostituito $request->boolean() con filter_var(..., FILTER_VALIDATE_BOOLEAN) per parsing corretto
    2. Aggiunto metodo massElimina() in IndividuoController per gestire eliminazione multipla con singolo form
    3. Aggiunta route POST /individui/mass-elimina in routes/web.php
    4. Rifattorizzata funzione JS deleteSelected() per usare singolo form con array ids[]
  • Files modificati:
    • app/Http/Controllers/IndividuoController.php: corretto elimina(), aggiunto massElimina()
    • routes/web.php: aggiunta route individui/mass-elimina
    • resources/views/individui/index.blade.php: rifattorizzata deleteSelected()

25 Maggio 2026 - Fix Export CSV Individui

  • Problema: "Esporta Tutti CSV" e "Esporta Selezionati CSV" non funzionavano
  • Cause:
    1. exportCSV() usava :visible (selettore jQuery) con querySelectorAll (JS nativo) che non lo supporta
    2. Export client-side esportava solo righe nel DOM (pagina corrente con pagination), non tutti i record
    3. Dati limitati a 5 colonne senza contatti email/telefono
  • Soluzione:
    1. Creato metodo exportCSV() server-side in IndividuoController con StreamedResponse
    2. Export completo con: Codice, Cognome, Nome, Data Nascita, Genere, Email, Telefono, Cellulare, Indirizzo, CAP, Città, Provincia, Note
    3. Email/Telefono/Cellulare recuperati dai contatti con preferenza per quelli primari
    4. Supporto per export tutti o solo selezionati tramite parametro ids[]
    5. Funzioni JS exportCSV() e exportSelectedCSV() ora puntano a /individui/export
  • Files modificati:
    • app/Http/Controllers/IndividuoController.php: aggiunto metodo exportCSV() con StreamedResponse
    • routes/web.php: aggiunta route GET /individui/export
    • resources/views/individui/index.blade.php: semplificate funzioni exportCSV() e exportSelectedCSV()

(Last updated: 25 Maggio 2026 - Report system, mass actions, gruppi deletion fix)

25 Maggio 2026 - Fix Eliminazione Gruppi Padre (Superadmin Only)

  • Problema: Eliminando un gruppo padre si otteneva pagina 404. Inoltre l'eliminazione di gruppi con figli era disponibile per tutti
  • Cause:
    1. Mancato detach delle tabelle pivot (gruppo_individuo, eventi_gruppi) prima della cancellazione
    2. Nessun controllo per limitare l'eliminazione di gruppi con sottogruppi al superadmin
  • Soluzione:
    1. Rifattorizzato destroy() in GruppoController: aggiunta verifica isSuperAdmin() per gruppi con figli
    2. Creato metodo destroyGruppo() helper che esegue detach pivot, cancella documenti fisici e poi cancella il gruppo
    3. Aggiornata vista tree-item.blade.php: pulsante elimina disabilitato per non-superadmin su gruppi con figli
  • Files modificati:
    • app/Http/Controllers/GruppoController.php: rifattorizzato destroy(), aggiunto destroyGruppo()
    • resources/views/gruppi/partials/tree-item.blade.php: pulsante elimina condizionato a superadmin per gruppi padre

25 Maggio 2026 - Mass Actions Eventi e Mailing List

  • Problema: Eventi e Mailing List non avevano funzionalità di eliminazione massiva
  • Soluzione:
    1. Aggiunti checkbox di selezione, pulsante "Elimina Selezionati" e modal di conferma in eventi/index.blade.php
    2. Aggiunti checkbox di selezione, pulsante "Elimina Selezionati" e modal di conferma in mailing-liste/index.blade.php
    3. Aggiunto metodo massElimina() in EventoController con detach di gruppi e responsabili
    4. Aggiunto metodo massElimina() in MailingListController con cancellazione contatti associati
    5. Aggiunte route POST /eventi/mass-elimina e POST /mailing-liste/mass-elimina
  • Files modificati:
    • app/Http/Controllers/EventoController.php: aggiunto massElimina()
    • app/Http/Controllers/MailingListController.php: aggiunto massElimina()
    • routes/web.php: aggiunte route mass-elimina per eventi e mailing-liste
    • resources/views/eventi/index.blade.php: aggiunta selezione massiva, modal conferma, JS
    • resources/views/mailing-liste/index.blade.php: aggiunta selezione massiva, modal conferma, JS

25 Maggio 2026 - Rimozione Menu Utilità Viste

  • Modifica: Rimosso dal sidebar menu la voce "Utilità Viste" (viste.index)
  • Nota: Le route e i controller per le viste rimangono funzionanti, solo il link sidebar è stato rimosso temporaneamente

25 Maggio 2026 - Sistema Report Completo

  • Funzionalità: Nuova pagina Report accessibile solo dal superadmin con:
    1. Panoramica database: statistiche rapide (individui, gruppi, eventi, documenti, mailing list, contatti)
    2. 12 Report predefiniti:
      • Individui per Genere
      • Individui per Fascia d'Età
      • Gerarchia Gruppi
      • Gruppi e Membri
      • Eventi del Mese
      • Documenti per Tipologia
      • Contatti per Tipo
      • Dettaglio Mailing List
      • Individui senza Contatti
      • Individui senza Gruppo
      • Eventi per Gruppo
      • Documenti Orfani
    3. Report personalizzati: creazione di report custom con selezione tipo dato, colonne, ordinamento e limite
    4. Export CSV: ogni report può essere esportato in CSV
  • Files creati:
    • app/Http/Controllers/ReportController.php: controller completo con 12 report + custom
    • app/Models/ReportCustom.php: modello per report personalizzati
    • database/migrations/2026_05_25_000001_create_report_custom_table.php: migration tabella report_custom
    • resources/views/report/index.blade.php: pagina principale report con card e form creazione
    • resources/views/report/result.blade.php: pagina visualizzazione risultati report
  • Routes aggiunte:
    • GET /report → report.index
    • GET /report/run → report.run
    • GET /report/export → report.export
    • GET /report/custom/{id} → report.run-custom
    • POST /report/custom → report.store-custom
    • DELETE /report/custom/{id} → report.destroy-custom
  • Sidebar aggiornata: link "Report" ora punta a route('report.index') con evidenziazione active

(Last updated: 25 Maggio 2026 - Fix sidebar @endif orfano, verifica completa sintassi)

25 Maggio 2026 - Fix Sidebar Errore Sintassi

  • 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
  • Files modificati:
    • app/Models/User.php: aggiunto 'report' a MODULES = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report']
    • app/Http/Controllers/ReportController.php: sostituiti tutti i check isSuperAdmin() con canAccess('report')
    • resources/views/layouts/adminlte.blade.php: sidebar mostra "Report" se canAccess('report') invece di isSuperAdmin()
  • Come abilitare un utente: Dal pannello Admin → Utenti, assegnare permesso di livello ≥ 1 (Lettura) per il modulo 'report'

25 Maggio 2026 - Viste Personalizzate per Colonne (Individui e Gruppi)

  • Funzionalità: Ogni utente può personalizzare quali colonne visualizzare nelle liste di Individui e Gruppi
  • Individui:
    • Colonne disponibili: Codice, Cognome, Nome, Email, Telefono
    • Il controller IndividuoController@index() ora passa $allColumns e $visibleColumns alla vista
    • Le colonne visibili sono determinate dalla VistaReport predefinita dell'utente
    • Modal "Colonne Visibili" mostra tutte le colonne con checkbox per toggle
    • Modal "Salva Vista" permette di salvare una vista con nome, colonne, e opzione predefinita
  • Gruppi:
    • Nuova vista tabella affiancata alla vista albero esistente
    • Toggle "Tabella/Albero" nella card header
    • Colonne disponibili: Nome, Descrizione, Diocesi, Livello, Gruppo Padre, Membri, Responsabili, Indirizzo, Città
    • Le colonne visibili sono determinate dalla VistaReport predefinita per tipo 'gruppi'
    • Salvataggio vista dedicato con route POST /gruppi/save-vista
    • Eliminazione vista con route DELETE /gruppi/vista/{id}
  • Model utilizzato: VistaReport (tabella viste_report) con campi: user_id, nome, tipo, colonne_visibili (JSON), is_default
  • Files modificati:
    • app/Http/Controllers/IndividuoController.php: index() passa $allColumns e $visibleColumns
    • app/Http/Controllers/GruppoController.php: index() rifattorizzato per vista tabella/albero, aggiunti saveVista() e deleteVista()
    • routes/web.php: aggiunte route gruppi/save-vista e gruppi/delete-vista
    • resources/views/individui/index.blade.php: colonne condizionali con @if(in_array(..., $visibleColumns))
    • resources/views/gruppi/index.blade.php: riscritta completamente con doppio mode (tabella/albero), modal salvataggio vista, toggle colonne

25 Maggio 2026 - Fix Vari Pagine (Individui, Eventi, Mailing, Documenti Upload)

  • Individui/Gruppi: Errore $tableColumns non definito risolto usando $allColumns
  • Eventi: Checkbox righe senza id impedivano il corretto funzionamento del label; aggiunto id="row-checkbox-{{ $evento->id }}"
  • Mailing List: Stesso problema checkbox, corretto con id univoci
  • Documenti upload: Funzione loadUploadTargets() chiamata ma non definita; aggiunta per popolare il dropdown target

25 Maggio 2026 - Fix Documento Avatar e Morph Map

  • Problema: Caricando un documento avatar associato a un individuo, errore "Class 'individuo' not found"
  • Causa: Il campo visibilita_target_type nel database conteneva nomi brevi ('individuo', 'gruppo') invece del namespace completo
  • Soluzione:
    1. Registrata Relation::morphMap() in AppServiceProvider@boot() per mappare nomi brevi a classi complete
    2. Aggiornato DocumentoController@store() per impostare correttamente visibilita_target_type in base alla visibilità
  • Files modificati:
    • app/Providers/AppServiceProvider.php: aggiunta morphMap per individuo, gruppo, evento, mailing, associazione, federazione
    • 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()
  • Files modificati:
    • app/Models/Gruppo.php: rimossa relazione responsabili(), mantenuti helper
    • app/Http/Controllers/GruppoController.php: rimossa eager loading responsabili
    • resources/views/gruppi/show.blade.php: corretto $child->responsabile$child->getResponsabili()->first()
    • resources/views/individui/show.blade.php: corretto $gruppo->responsabile$gruppo->getResponsabili()->first()

25 Maggio 2026 - Fix Caricamento Documenti in Pagina Gruppi

  • Funzionalità: Aggiunto modale "Carica Documento" nella pagina gruppi/show per upload diretto
  • Logica Responsabili: Solo i membri del gruppo possono essere selezionati come responsabili
    • In create.blade.php: il dropdown responsabili si aggiorna dinamicamente quando si aggiungono/rimuovono membri
    • Funzione JS updateResponsabiliFromMembri() sincronizza il select responsabili con i membri aggiunti
  • Files modificati:
    • resources/views/gruppi/show.blade.php: aggiunto modale upload documento, alert success/error
    • resources/views/gruppi/create.blade.php: logica JS per vincolare responsabili ai soli membri

(Last updated: 25 Maggio 2026 - Fix report eventi_calendario, colonna data)

25 Maggio 2026 - Fix Report Eventi Calendario

  • Problema: Report "Eventi del Mese" falliva con "Unknown column 'data_inizio' in 'WHERE'"
  • Causa: Il report usava data_inizio che non esiste nella tabella eventi. La colonna corretta è data_specifica
  • Soluzione: Sostituito data_inizio con data_specifica in reportEventiCalendario() e reportEventiPerGruppo()
  • Files modificati:
    • app/Http/Controllers/ReportController.php: righe 389-397 e 532, corretto nome colonna

(Last updated: 25 Maggio 2026 - Fix report eventi_calendario, colonna data)

25 Maggio 2026 - Fix Gruppi Table Hierarchy

  • Problema: La visualizzazione a tabella dei gruppi non mostrava correttamente la gerarchia padre-figlio
  • Soluzione:
    • Aggiunto metodo sortGruppiHierarchically() in GruppoController per ordinare i gruppi in modo gerarchico
    • La tabella ora mostra prima i gruppi root, poi i loro figli in ordine annidato
    • Aggiunta eager loading avatar per supportare il futuro avatar dei gruppi
  • Files modificati:
    • app/Http/Controllers/GruppoController.php: aggiunto sortGruppiHierarchically(), addChildrenRecursive(), aggiunto 'avatar' a eager loading

25 Maggio 2026 - Group Avatar Support

  • Funzionalità: I gruppi ora possono avere un avatar/logo come gli individui
  • Implementazione:
    • Aggiunto metodo avatar() in Gruppo.php (relazione HasOne a Documento con tipologia 'avatar')
    • Aggiunto accessor getAvatarUrlAttribute() per ottenere l'URL dell'avatar
    • Eager loading avatar aggiunto in GruppoController@index()
    • View gruppi/index.blade.php aggiornata per mostrare l'avatar nella tabella
    • View gruppi/show.blade.php aggiornata per mostrare l'avatar del gruppo
    • View gruppi/create.blade.php e edit.blade.php aggiornate per permettere upload avatar
  • Files modificati:
    • app/Models/Gruppo.php: aggiunto avatar(), getAvatarUrlAttribute()
    • app/Http/Controllers/GruppoController.php: aggiunto eager loading avatar
    • resources/views/gruppi/index.blade.php: colonna avatar nella tabella
    • resources/views/gruppi/show.blade.php: avatar nella header card
    • resources/views/gruppi/create.blade.php e edit.blade.php: upload avatar

26 Maggio 2026 - Event Types System (Tipologie Eventi)

  • Nuova tabella: tipologie_eventi con migration + seed dati default (catechesi, liturgia, animazione, formazione, incontro, riunione, festa, altro)
  • Nuovo modello: App\Models\TipologiaEvento — identico pattern a TipologiaDocumento e Ruolo:
    • $fillable: nome, descrizione, ordine, attiva
    • Metodi: eventi() (hasMany), attive(), opzioni()
    • FK via nome (string) → eventi.tipo_evento (stesso pattern Documento.tipologia → TipologiaDocumento.nome)
  • Admin CRUD: 4 nuovi metodi in ImpostazioniController: tipologieEventiStore(), tipologieEventiUpdate(), tipologieEventiDestroy(), tipologieEventiReorder()
  • Routes (5 nuove): /impostazioni/tipologie-eventi/*
  • 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
  • Files creati:
    • database/migrations/2026_05_26_000001_create_tipologie_eventi_table.php
    • app/Models/TipologiaEvento.php
  • Files modificati:
    • app/Http/Controllers/ImpostazioniController.php
    • app/Http/Controllers/EventoController.php
    • app/Models/Evento.php
    • routes/web.php
    • resources/views/impostazioni/index.blade.php
    • resources/views/layouts/adminlte.blade.php
    • resources/views/eventi/create.blade.php
    • resources/views/eventi/edit.blade.php
    • resources/views/eventi/show.blade.php
    • resources/views/eventi/index.blade.php

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 reportis_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)
    • Settato is_admin = trueisSuperAdmin() = true, bypass totale ACL
    • canAccess('report') = true, tutti i menu sidebar ora visibili
  • Nuovo comando: app/Console/Commands/SyncUserPermissions.phpphp artisan users:sync-permissions
    • 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)
  • Files modificati:
    • app/Models/User.php: MODULES + 'settings'
    • resources/views/layouts/adminlte.blade.php: isSuperAdmin → canManage('settings')
    • app/Http/Middleware/AdminOnly.php: isSuperAdmin || canManage('settings')
    • app/Http/Controllers/ImpostazioniController.php: viste → settings in 16 authorize calls
    • app/Http/Controllers/Admin/EmailSettingsController.php: aggiunte 5 authorizeWrite('settings')
    • 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()
    • resources/views/admin/utenti/create.blade.php: hidden field, settings default 0
    • app/Http/Controllers/Admin/UtenteController.php: removed ?? false
    • app/Http/Controllers/Auth/AuthController.php: added report/settings defaults

(Last updated: 26 Maggio 2026 - Fix Salvataggio is_admin)

26 Maggio 2026 - Sender Accounts (Mittenti Aggiuntivi solo invio)

  • Nuova tabella: sender_accounts con migration 2026_05_26_000002
  • Colonne: id, email_address (UNIQUE), email_name, smtp_host, smtp_port, smtp_encryption, smtp_username, smtp_password (Crypt), reply_to, verify_email, note, is_active, timestamps
  • Model SenderAccount: $fillable, $casts, metodi:
    • getDecryptedPassword() — decripta password SMTP con fallback
    • sanitizeName() — pulisce il nome visualizzato
    • getReplyTo() — priorità: reply_toverify_email → null
    • buildDsn() — costruisce DSN Symfony Mailer
    • 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
  • Migration 2026_05_26_000003: aggiunte colonne log_falliti (JSON), mittente_nome, mittente_email a mailing_messaggi
  • Model MailingMessaggio: aggiornato $fillable e $casts per log_falliti (array)
  • CRUD in EmailSettingsController: 4 nuovi metodi protetti da authorizeWrite('settings'):
    • senderStore() — crea nuovo sender con password criptata
    • senderUpdate() — modifica sender, password opzionale
    • senderDestroy() — elimina sender
    • senderTestSmtp() — test invio email via sender
  • Routes (6 nuove):
    • POST /impostazioni/sendersenderStore
    • PUT /impostazioni/sender/{id}senderUpdate
    • DELETE /impostazioni/sender/{id}senderDestroy
    • POST /impostazioni/sender/{id}/testsenderTestSmtp
    • GET /mailing/invioMailingController@invio (prima mancante)
    • POST /mailing/invio/elaboraMailingController@invioElabora (prima mancante)
  • EmailController@compose: passa $senderAccounts alla view; nuova view mostra dropdown "Da (mittente)" prima dei destinatari
  • EmailController@send modificato:
    • Se mittente_id > 0 → usa SenderAccount::sendEmail() con allegati; salta store sent message
    • Se mittente_id = 0 o assente → comportamento attuale con EmailSetting
    • Processa allegati allegati[] e documenti_selezionati
    • Nuovo helper: sendWithSender(), processAttachments(), resolveAttachmentPaths()
    • sendViaImap() ora accetta $attachmentPaths e li attacha all'email
  • MailingController riscritto con invio SMTP reale:
    • invia() — invio 1:1 a ogni destinatario, log su mailing_messaggi
    • invioElabora() — invio massivo 1:1 per ogni email da liste multiple, log su mailing_messaggi
    • Tracking: stato in_codainviato/parziale/fallito, conteggi ok/fail, log_falliti JSON
    • Summary email a verify_email dopo invio massivo
    • sendViaSystem() — invio via EmailSetting (fallback quando nessun sender selezionato)
    • resolveMailingAttachmentPaths() — risolve path file da Documenti selezionati
  • View email-settings/index.blade.php:
    • Nuovo tab "Mittenti Aggiuntivi" nella sidebar
    • Tabella con email, nome, SMTP, Reply-To, Verify, Stato, Azioni (Modifica/Test/Elimina)
    • Modal CRUD per creare/modificare sender con tutti i campi
    • JS: resetSenderForm(), editSender(id), testSenderSmtp(id)
    • Sezione informativa "Come funziona"
  • View email/compose.blade.php: dropdown "Da (mittente)" se ci sono sender attivi (default: Sistema)
  • View mailing/nuovo.blade.php: dropdown "Mittente" se ci sono sender attivi
  • View mailing/invio.blade.php: dropdown "Mittente" se ci sono sender attivi
  • Files creati:
    • database/migrations/2026_05_26_000002_create_sender_accounts_table.php
    • database/migrations/2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php
    • app/Models/SenderAccount.php
  • Files modificati:
    • app/Models/MailingMessaggio.php
    • app/Http/Controllers/Admin/EmailSettingsController.php
    • app/Http/Controllers/EmailController.php
    • app/Http/Controllers/MailingController.php
    • routes/web.php
    • resources/views/admin/email-settings/index.blade.php
    • resources/views/email/compose.blade.php
    • resources/views/mailing/nuovo.blade.php
    • resources/views/mailing/invio.blade.php

26 Maggio 2026 - Export ICS/iCal Eventi

  • Nuovo Service: app/Services/IcsExportService.php — generazione file ICS RFC 5545
    • generateSingle(Evento) — esporta un singolo evento
    • generateCollection(iterable) — esporta multipli eventi in un unico file ICS
    • Supporto RRULE completo per eventi ricorrenti:
      • settimanaleFREQ=WEEKLY;BYDAY=MO
      • mensileFREQ=MONTHLY;BYSETPOS=1;BYDAY=SU (con opzionale BYMONTH)
      • annualeFREQ=YEARLY;BYMONTH=6;BYMONTHDAY=15
      • altroFREQ=WEEKLY;BYDAY=MO
    • Timezone Europe/Rome per eventi con ora, DATE (all-day) per eventi senza ora
    • LOCATION, DESCRIPTION, SUMMARY, UID univoci
    • Line folding a 75 caratteri RFC 5545
    • Escape di ;, ,, \, \n
  • Nuovi metodi in EventoController:
    • exportIcs(int $id, IcsExportService $ics) — StreamedResponse download singolo evento
    • exportSelectedIcs(Request $request, IcsExportService $ics) — StreamedResponse con filtro per ID selezionati o ricerca corrente
  • Routes:
    • GET /eventi/{evento}/export-icseventi.export-ics
    • POST /eventi/export-icseventi.export-selected-ics
  • View updates:
    • 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
  • Files creati:
    • app/Services/IcsExportService.php
  • Files modificati:
    • app/Http/Controllers/EventoController.php
    • routes/web.php
    • resources/views/eventi/show.blade.php
    • resources/views/eventi/index.blade.php
    • resources/views/eventi/calendar.blade.php

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)
  • Files modificati:
    • app/Http/Controllers/ImpostazioniController.php: import + compact SenderAccount
    • resources/views/impostazioni/index.blade.php: pill Mittenti Aggiuntivi, tab-pane email-mittenti, modale sender modifica/crea, funzioni JS (resetSenderForm, editSender, deleteSender, testSenderSmtp)
    • app/Http/Controllers/Admin/EmailSettingsController.php: senderDestroy() con expectsJson()
    • resources/views/layouts/adminlte.blade.php: rimossa voce Impostazioni Email dal sidebar

(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
  • Routes (2 nuove): GET /storage-repositories/{id}/download, GET /storage-repositories/{id}/preview
  • 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.

27 Maggio 2026 - Password Reset & Profile

  • 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
    • Views: auth/forgot-password.blade.php, auth/reset-password.blade.php, auth/emails/reset-password.blade.php
  • 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
  • 6 nuovi report predefiniti (18 totali): documenti_completo, scadenze_documenti, individui_completo, gruppi_completo, eventi_completo, contatti_completo
  • Tipologia filter: I report documenti_completo e scadenze_documenti hanno dropdown inline Select2 per filtro tipologia nella card dell'index
  • Report index view: Select2 multiselect per colonne, Select2 tipologia filter, sort direction, nuovi bottoni CSV export per custom reports

27 Maggio 2026 - Document Manager Rework (Cartelle + Storage Settings)

  • Migrations (3 nuove):
    • 2026_05_27_000001_create_documenti_cartelle_table.php — tabella documenti_cartelle con parent_id FK self, nome, timestamps
    • 2026_05_27_000002_add_cartella_id_to_documenti.php — colonna cartella_id FK → documenti_cartelle.id su documenti
    • 2026_05_27_000003_add_documenti_storage_to_app_settings.php — colonne documenti_storage_disk (default local) e documenti_storage_path (default documenti) in app_settings
  • Model DocumentoCartella: relazioni parent(), children(), documenti(), helper ancestors(), fullPath()
  • Model Documento: aggiunto cartella_id in $fillable, nuova relazione cartella() (BelongsTo → DocumentoCartella). Tutte le relazioni esistenti (eventi/gruppi/individui/target/user/tenant) INTATTE
  • Model AppSetting: aggiunti metodi getDocumentiStorageDisk(), getDocumentiStoragePath(), getDocumentiStorageAbsolutePath()
  • DocumentoCartellaController: CRUD store/update/destroy + endpoint JSON breadcrumb(id) per breadcrumb navigazione
  • DocumentoController riscritto:
    • index(): filtra per folder_id GET param, passa $cartelle (struttura nidificata), $currentFolder, $breadcrumb alla view
    • store(): accetta cartella_id opzionale, storage su disco dinamico da AppSetting
    • download/preview/destroy/massDestroy: storage disk risolto dinamicamente via resolveStorageDisk()
    • Tutti gli altri metodi (update, checkLinks, massUpdate, massAssociate) INALTERATI
  • ImpostazioniController: saveAppSettings() valida e salva documenti_storage_disk (local|public) e documenti_storage_path
  • View documenti/index.blade.php: riscritto come file manager a 3 colonne:
    • Sidebar sinistra (col-md-3): folder tree con list-group e partial ricorsivo, link "Tutti i documenti", pulsante "+" Nuova Cartella
    • Contenuto principale (col-md-9): breadcrumb navigazione, toolbar azioni massive, griglia card (col-md-4 each) con icona MIME-type, nome, tipologia badge, dimensione, data, autore, pulsanti azioni
    • Stessi 6 modali esistenti (upload, associa, cambia tipo, preview, delete confirm, mass delete confirm) + nuovo modale "Nuova Cartella" AJAX
    • JS: nuova funzione per submit AJAX del form nuova cartella con reload pagina
  • View documenti/_folder_tree.blade.php: partial ricorsivo con @include per rendering folder tree annidato con indentazione progressiva
  • View impostazioni/index.blade.php: nuova sezione "Repository Documenti" (tab-pane #documenti) con select disk, input path, path assoluto calcolato + alert informativo
  • Routes (5 nuove):
    • POST /documenti/cartelleDocumentoCartellaController@store
    • PUT /documenti/cartelle/{id}DocumentoCartellaController@update
    • DELETE /documenti/cartelle/{id}DocumentoCartellaController@destroy
    • GET /documenti/cartelle/{id}/breadcrumbDocumentoCartellaController@breadcrumb
    • POST /documenti/mass-moveDocumentoController@massMove
  • 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 "+".
  • Verifica: sintassi PHP ok, view cache ok, 8/9 test pass (solo ExampleTest pre-esistente fallisce)
  • Files creati:
    • database/migrations/2026_05_27_000001_create_documenti_cartelle_table.php
    • database/migrations/2026_05_27_000002_add_cartella_id_to_documenti.php
    • database/migrations/2026_05_27_000003_add_documenti_storage_to_app_settings.php
    • app/Models/DocumentoCartella.php
    • app/Http/Controllers/DocumentoCartellaController.php
    • resources/views/documenti/_folder_tree.blade.php
  • Files modificati:
    • app/Models/Documento.php: cartella_id fillable + cartella() relation
    • app/Models/AppSetting.php: metodi storage documenti
    • app/Http/Controllers/DocumentoController.php: riscritto con filtro folder + storage dinamico
    • app/Http/Controllers/ImpostazioniController.php: saveAppSettings validazione storage
    • resources/views/documenti/index.blade.php: riscritto (file manager 3 colonne)
    • 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. localpublic 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.

(Last updated: 28 Maggio 2026 - Folder icons, associa button, updateModal target, saveAppSettings fix)

28 Maggio 2026 - Folder Icons, Associa Button, UpdateModal Target, saveAppSettings Fix

  • 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.
  • Files modificati:
    • app/Http/Controllers/ImpostazioniController.php: saveAppSettings fallback valori esistenti
    • app/Http/Controllers/DocumentoController.php: massUpdate esteso con target fields
    • resources/views/documenti/_folder_tree.blade.php: always-visible icons + move button
    • resources/views/documenti/index.blade.php: CSS always-visible, updateModal target dropdown, moveFolder() JS, showAssociateModal() JS, associa button su ogni file
  • Stato: 18/19 test pass. php -l OK. php artisan view:cache OK.

(Last updated: 28 Maggio 2026 - Single-document "Cambia tipo/contesto" + mailing list fix in updateModal)

28 Maggio 2026 - Single-document "Cambia tipo/contesto" + mailing list fix

  • Aggiunto pulsante singolo "Cambia tipo/contesto" su ogni documento:
    • Grid view (card footer): bottone fa-tag dopo "Associa"
    • List view (colonna azioni): stesso bottone per riga
    • Nuova funzione JS showUpdateModal(id) pre-popola updateIds col singolo ID
  • Fix mailing list in updateModal: mailingLists ora mappato a {id, label} via .map(fn($m) => ['id' => $m->id, 'label' => $m->nome])
  • Files modificati:
    • resources/views/documenti/index.blade.php: bottoni su grid+list, funzione showUpdateModal(), mapping mailing list corretto
  • Stato: php -l OK. php artisan view:cache OK.

28 Maggio 2026 - Folder action icons (grid/list view) + mass download ZIP

  • Aggiunte icone azioni cartelle in griglia e lista (non solo sidebar tree):
    • Grid view: card-footer con pulsanti Rinomina, Sposta, Elimina per ogni cartella
    • List view: colonna azioni con pulsanti Rinomina, Sposta, Elimina per ogni riga cartella
  • Nuova mass action "Scarica": pulsante nella toolbar azioni massive
    • Crea un archivio ZIP con ZipArchive contenente i file selezionati
    • Supporta sia documenti locali che da repository remoti
    • Download monouso (response()->download()->deleteFileAfterSend(true))
    • 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 addslashesjson_encode per i nomi repository negli onclick.
  • Files modificati:
    • app/Http/Controllers/DocumentoController.php: destroy() redirect default url()->previous()
    • resources/views/documenti/index.blade.php: 10 onclick con json_encode invece di addslashes
    • resources/views/documenti/_folder_tree.blade.php: 3 onclick con json_encode invece di addslashes
  • Stato: 18/19 test pass. php -l OK. php artisan view:cache OK. JS braces 182/182.

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.
  • Files modificati:
    • app/Services/StorageRepositoryService.php: encryptSensitiveConfig() con existingConfig, buildGoogleDrive() validation, testConnection() credential check, listContents() credential check
    • app/Http/Controllers/StorageRepositoryController.php: update() passa existing config
  • 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).
  • Status visivo: badge verde "Autorizzato" se refresh_token presente, "Non autorizzato" altrimenti
  • Files modificati:
    • routes/web.php: 2 nuove route OAuth
    • app/Http/Controllers/StorageRepositoryController.php: oauthRedirect(), oauthCallback()
    • 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().
  • Files modificati:
    • app/Services/StorageRepositoryService.php: riga 122, '/'''
  • 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').
  • Files modificati:
    • resources/views/layouts/adminlte.blade.php: riga 259, aggiunto <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  • Stato: view cache pulita (php artisan view:clear), tutti i file lint OK. php artisan view:cache OK.

28 Maggio 2026 - Fix authType WebDAV (Nextcloud)

  • Problema: Test connessione Nextcloud dava "Unsupported operand types: string & int"
  • Causa: buildWebDAV() passava 'authType' => 'basic' (stringa) a Sabre\DAV\Client, che esegue bitwise AND ('basic' & AUTH_BASIC) — incompatibile
  • Soluzione: Convertito stringa → int tramite match: 'basic' → WebDAVClient::AUTH_BASIC (1), 'digest' → AUTH_DIGEST (2), 'ntlm' → AUTH_NTLM (4)
  • Stato test Nextcloud: AuthType non dà più errore. Ora fallisce con "Unauthorized" — le credenziali Nextcloud effettive non sono corrette.
  • Drive-Segreteria-ADIM: Credenziali OK, connessione OK, ma 0 elementi nella root. Verificare root_folder_id.

(Last updated: 28 Maggio 2026 - Fix authType WebDAV + verify Drive state)