pre version 2
This commit is contained in:
@@ -3,6 +3,28 @@
|
||||
## Obiettivo
|
||||
App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
|
||||
|
||||
## Riassunto Generale
|
||||
|
||||
| Area | Stato | Ultima modifica |
|
||||
|------|-------|-----------------|
|
||||
| **Core** (Individui, Gruppi, Eventi, Documenti, Mailing) | ✅ Completo | CRUD, relazioni, filtri, paginazione, ACL, export |
|
||||
| **Tag System** | ✅ Completo | morphToMany su 5 entità, filtri OR/AND, report, ricerca unificata |
|
||||
| **Viste Colonne** | ✅ Completo | Per-User column visibility/width/order, resize/drag-reorder, print |
|
||||
| **Email (IMAP/SMTP)** | ✅ Completo | Sync messaggi, firma, allegati, mittenti multipli, XOAUTH2 OAuth |
|
||||
| **Google OAuth 2.0** | ✅ Implementato | Unified (Email/Drive/Calendar), UI impostazioni, XOAUTH2 SMTP+IMAP |
|
||||
| **Repository Remoti** | ✅ Completo | WebDAV, Google Drive, OAuth |
|
||||
| **Calendario** | ✅ Completo | Google Calendar, CalDAV, sync bidirezionale, pulsante sync |
|
||||
| **Report** | ✅ Completo | Custom report con colonne/filtri salvabili, tag filter |
|
||||
| **Help / Documentazione** | ✅ Completo | Tab Google Drive + Calendar in help page, PDF export |
|
||||
| **Distribuzione** | ✅ Completo | build-dist.sh, fix path/cache/permessi/symlink |
|
||||
|
||||
### Cronologia modifiche principali
|
||||
- **2026-06-07**: Fix colonne mancanti (eventi, gruppo_individuo)
|
||||
- **2026-06-08**: build-dist.sh fix, logo remote fix, 18 migration guardie, help Calendar, sync pulsante, form annidato fix, checkbox hidden fallback, DiagnoseEmail command
|
||||
- **2026-06-09**: Tag completato (MailingList, Report, Ricerca), @stack scripts fix, storeCustom tag_filter fix, MailingList edit JS fix
|
||||
- **2026-06-10**: Per-User Column Views, ColumnManager JS, editVista data-* fix, 405 AJAX fix
|
||||
- **2026-06-17**: Google OAuth 2.0 unificato (revert Socialite, unified OAuth per Email/Drive/Calendar, XOAUTH2 SMTP+IMAP, UI impostazioni)
|
||||
|
||||
## Funzionalità Implementate
|
||||
|
||||
### Page-length selector
|
||||
@@ -380,3 +402,179 @@ if (!empty($contatto['individuo_id'])) { create }
|
||||
**Fix**:
|
||||
1. `VistaReportController@update` (linea 101-103): aggiunto `if ($request->expectsJson()) { return response()->json([...]); }` — così il fetch riceve JSON 200, non un redirect 302.
|
||||
2. Fetch headers in `table-settings.blade.php`: aggiunto `'Accept': 'application/json'` — necessario perché `expectsJson()` controlla l'header `Accept`, non `Content-Type`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-17 — Google OAuth 2.0 Unificato (Email/Drive/Calendar)
|
||||
|
||||
**Obiettivo**: Implementare OAuth 2.0 centralizzato per tutti i servizi Google (Gmail email, Google Drive, Google Calendar), con UI nelle impostazioni e supporto XOAUTH2 per SMTP/IMAP via Symfony Mailer + ImapEngine. Sistema password attuale deve funzionare in parallelo.
|
||||
|
||||
### Cosa è stato fatto
|
||||
|
||||
**Enum** (`app/Enums/GoogleService.php`):
|
||||
- Definisce 3 casi: `Email`, `Drive`, `Calendar`
|
||||
- Metodi: `scopes()` (array scopes OAuth per servizio), `label()` (nome italiano), `icon()`, `description()` (testo help)
|
||||
|
||||
**Migration** (`2026_06_17_000001_create_google_oauth_connections_table.php`):
|
||||
- Tabella `google_oauth_connections`: id, user_id (FK), service, email, access_token, refresh_token, expires_at, timestamps
|
||||
- Unique constraint `[user_id, service, email]`
|
||||
- Già eseguita in batch 39
|
||||
|
||||
**Migration** (`2026_06_17_000002_add_oauth_columns.php`):
|
||||
- `email_settings`: aggiunti `auth_method` (varchar 20, default 'password') + `google_oauth_connection_id` (FK → google_oauth_connections, nullOnDelete)
|
||||
- `sender_accounts`: stesse colonne
|
||||
- Guardia `Schema::hasColumn` per idempotenza
|
||||
- Già eseguita in batch 39
|
||||
|
||||
**Model** (`app/Models/GoogleOAuthConnection.php`):
|
||||
- `$fillable`: user_id, service, email, access_token, refresh_token, expires_at
|
||||
- `$casts`: expires_at → datetime
|
||||
- `user()`: BelongsTo User
|
||||
- `isExpired()`: controllo scadenza token
|
||||
- `refresh()`: refresh automatico via GoogleOAuthService
|
||||
- `getValidAccessToken()`: refresh se expired, ritorna token valido
|
||||
|
||||
**Service** (`app/Services/GoogleOAuthService.php`):
|
||||
- `buildClient()`: configura Google_Client con client_id/secret/redirect_uri, access_type=offline, prompt=consent
|
||||
- `getAuthUrl(service)`: URL di autorizzazione con scopes specifici per servizio
|
||||
- `handleCallback(code, service)`: scambia code per token, crea/aggiorna GoogleOAuthConnection
|
||||
- `getAccessToken(connection)`: cache 5 min, refresh automatico se expired
|
||||
- `refreshToken(connection)`: fetch nuovo token via refresh_token, elimina se refresh fallisce
|
||||
- `revoke(connection)`: revoca token Google, elimina record DB
|
||||
- `createXoAuth2SmtpTransport(email, token)`: SMTP transport con solo XOAuth2Authenticator (evita tentativi PLAIN/LOGIN)
|
||||
- `getConnectionStatus()`: restituisce array con stato connessioni per ogni servizio
|
||||
|
||||
**Controller** (`app/Http/Controllers/GoogleOAuthController.php`):
|
||||
- `redirect(service)`: reindirizza a Google OAuth
|
||||
- `callback(request)`: gestisce risposta Google, crea connessione, redirect a impostazioni con success/error
|
||||
- `revoke(connection)`: revoca connessione (solo proprietario)
|
||||
- `status()`: JSON con stato connessioni
|
||||
|
||||
**Config/Route changes**:
|
||||
- `config/services.php`: sezione `google` con `client_id`, `client_secret`, `redirect` (usa `config('app.url')` per compatibilità CLI)
|
||||
- `routes/web.php`: 4 nuove route (`google-oauth.redirect`, `google-oauth.callback`, `google-oauth.revoke`, `google-oauth.status`) + `use App\Http\Controllers\GoogleOAuthController`
|
||||
|
||||
**View** (`resources/views/impostazioni/_google-oauth.blade.php`):
|
||||
- Card con lista servizi (Gmail, Drive, Calendar), badge connesso/non connesso, pulsanti Connetti/Disconnetti
|
||||
- JS: auto-click tab se `?tab=google` in URL (dopo callback)
|
||||
|
||||
**Modifiche EmailSetting.php**:
|
||||
- `$fillable` + `auth_method`, `google_oauth_connection_id`
|
||||
- `$casts` + `google_oauth_connection_id` → integer
|
||||
- `googleOAuthConnection()`: BelongsTo relationship
|
||||
- `getImapConfig()`: se auth_method=oauth, imposta `authentication => 'oauth'` e password = access token
|
||||
- `getDecryptedPassword()`: se OAuth, risolve token invece di decrittare password
|
||||
- `getDecryptedSmtpPassword()`: stessa logica per SMTP
|
||||
- `resolveOAuthToken()`: carica relazione + chiama GoogleOAuthService::getAccessToken
|
||||
- `getImapClient()`: passa `authentication => 'oauth'` se auth_method=oauth
|
||||
- `getSmtpMailer()`: se OAuth → `buildOAuthSmtpMailer()` (XOAuth2 solo), altrimenti DSN normale
|
||||
- `buildOAuthSmtpMailer()`: EsmtpTransport + XOAuth2Authenticator
|
||||
- `buildSmtpDsn()`: estratto da EmailSettingsController (password decrittata)
|
||||
|
||||
**Modifiche SenderAccount.php**:
|
||||
- Stesso pattern: `auth_method`, `google_oauth_connection_id`, relationship, `resolveOAuthToken()`
|
||||
- `getDecryptedPassword()`: OAuth-aware
|
||||
- `sendEmail()`: se OAuth → `buildOAuthMailer()` (XOAuth2), altrimenti DSN
|
||||
- `buildOAuthMailer()`: EsmtpTransport + XOAuth2Authenticator
|
||||
|
||||
**Modifiche EmailSettingsController.php**:
|
||||
- `index()`: passa `$googleStatus` → `GoogleOAuthService::getConnectionStatus()`
|
||||
- `save()`: validazione + salvataggio `auth_method`, `google_oauth_connection_id`
|
||||
- `testSmtp()`: ora usa `$settings->getSmtpMailer()` invece di costruire DSN manualmente
|
||||
- `senderStore()`/`senderUpdate()`: validazione + salvataggio `auth_method`, `google_oauth_connection_id`
|
||||
|
||||
**Modifiche impostazioni/index.blade.php**:
|
||||
- Sidebar link "Google" (icona `fab fa-google`) prima del link "Calendario"
|
||||
- Tab-pane con `@include("impostazioni._google-oauth")` prima del tab calendario
|
||||
|
||||
### Note tecniche
|
||||
- **XOAUTH2**: Symfony Mailer ha già `XOAuth2Authenticator` in vendor (`vendor/symfony/mailer/Transport/Smtp/Auth/XOAuth2Authenticator.php`), invia `AUTH XOAUTH2 user=<email>\1auth=Bearer <token>\1\1`
|
||||
- **ImapEngine**: già supporta `'authentication' => 'oauth'` in config → `AUTHENTICATE XOAUTH2 <base64>`
|
||||
- **google/apiclient**: presente in vendor, usato per Google_Client (buildClient, fetchAccessTokenWithAuthCode, refresh, revoke). `Google\Service\Gmail` NON disponibile ma non necessario (XOAUTH2 bypassa API REST)
|
||||
- **Permessi filesystem**: nuovi file copiati con sudo (www-data:www-data, 644), index.blade.php modificato con sed
|
||||
- **refresh token**: richiede `access_type=offline` + `prompt=consent` + `includeGrantedScopes=true` per garantire refresh token sempre al primo auth
|
||||
- **config('app.url')** in services.php invece di `url()` helper (url() fallisce in CLI context)
|
||||
|
||||
### 2026-06-17 — Bug Fix: Tag massivo e per-riga per Eventi + Documenti
|
||||
|
||||
**Problemi riscontrati**:
|
||||
1. Eventi: modal mass tag HTML corrotto (`×` → `<div class="modal fade" id="deleteModal"`)
|
||||
2. Eventi + Documenti: nessun feedback visivo dopo submit mass tag (mancava `session('error')` su Eventi; mancavano ENTRAMBI su Documenti)
|
||||
3. Eventi + Documenti: nessuna icona TAG per-riga nelle azioni
|
||||
4. Documenti: mancava funzione `openSingleTag()` JS
|
||||
|
||||
**Fix**:
|
||||
1. `resources/views/eventi/index.blade.php:340` — Ripristinato `×` nel close button del mass tag modal
|
||||
2. `resources/views/eventi/index.blade.php:22-27` — Aggiunto `@if(session('error'))` alert danger
|
||||
3. `resources/views/eventi/index.blade.php:283-285` — Aggiunto pulsante per-riga `fa-tags` → `openSingleTag(eventId)`
|
||||
4. `resources/views/eventi/index.blade.php:427-431` — Aggiunta funzione JS `openSingleTag(eventId)`
|
||||
5. `resources/views/documenti/index.blade.php:21-32` — Aggiunti `@if(session('success'))` + `@if(session('error'))` alert
|
||||
6. `resources/views/documenti/index.blade.php:283-285` e `484-486` — Aggiunto pulsante per-riga `fa-tags` in vista griglia + lista
|
||||
7. `resources/views/documenti/index.blade.php:1318-1323` — Aggiunta funzione JS `openSingleTag(docId)` con querySelector per `.doc-checkbox[value=...]`
|
||||
8. Verifica JS brace balance: OK su entrambi (diff=0)
|
||||
9. Verifica PHP lint: OK su entrambi i controller
|
||||
|
||||
## 2026-06-17 — Bug Fix: Documenti + Eventi (syntax error, strict_types, validazione)
|
||||
|
||||
### Problemi trovati e fixati
|
||||
|
||||
**1. CRITICO — Syntax error PHP in DocumentoController.php:239**
|
||||
- **Problema**: Parentesi `)` extra alla fine del ternario: `/documenti');` invece di `/documenti';`
|
||||
- **Root cause**: `$redirect = ... : '/documenti');` — parentesi di chiusura in eccesso
|
||||
- **Effetto**: PHP parse error: `Unclosed '{' on line 197 does not match ')'` — l'intero metodo `update()` era rotto
|
||||
- **Fix**: Rimosso `)` extra
|
||||
|
||||
**2. declare(strict_types=1) mancante** (violazione AGENTS.md)
|
||||
- **Problema**: 5 file del dominio documenti/eventi non avevano `declare(strict_types=1)`, obbligatorio per PHP 8.4
|
||||
- **File fixati**: `Documento.php`, `Evento.php`, `EventoController.php`, `TipologiaDocumento.php`, `EventoDocumentoController.php`
|
||||
|
||||
**3. Validazione assente `contesto_tipo` in DocumentoController@update**
|
||||
- **Problema**: Il campo `contesto_tipo` veniva letto dal raw request (`$request->contesto_tipo`) senza validazione. Un utente poteva forzare valori arbitrari.
|
||||
- **Fix**: Aggiunto `'contesto_tipo' => 'nullable|in:individuo,gruppo,evento,mailing'` alle regole di validazione. Ora si usa il valore validato (`$contestoTipo`) invece del raw request.
|
||||
|
||||
**4. Tipologia hardcoded in DocumentoController@store**
|
||||
- **Problema**: `store()` usava `'tipologia' => 'required|in:avatar,galleria,documento,statuto,altro'` (hardcoded), mentre `update()` usava `TipologiaDocumento::opzioni()` (dinamico). Nuove tipologie aggiunte via UI non sarebbero state accettate da `store()`.
|
||||
- **Fix**: Allineato `store()` a usare `TipologiaDocumento::opzioni()` come `update()`.
|
||||
|
||||
**5. JS brace balance verificato**
|
||||
- `resources/views/documenti/index.blade.php` → OK (diff=0)
|
||||
- `resources/views/eventi/index.blade.php` → OK (diff=0)
|
||||
|
||||
### Verifica
|
||||
- `php -l` su tutti e 6 i file modificati: nessun errore di sintassi
|
||||
|
||||
## 2026-06-17 — Fix: Eventi mass tag "nessun evento selezionato"
|
||||
**Problema**: La toolbar button chiamava `$('#massTagModal').modal('show')` direttamente, affidandosi a un listener `show.bs.modal` per popolare l'hidden field `massTagIds`. Se l'evento non veniva intercettato, `ids` restava vuoto e il server rispondeva "Nessun evento selezionato".
|
||||
|
||||
**Fix** (`resources/views/eventi/index.blade.php`):
|
||||
1. Toolbar button: `onclick="$('#massTagModal').modal('show')"` → `onclick="showMassTagModal()"`
|
||||
2. Rimosso listener `show.bs.modal` che popolava i campi
|
||||
3. Aggiunta funzione `showMassTagModal()` (stesso pattern di Gruppi/Individui) che valida `getSelectedIds()` e popola i campi **prima** di aprire la modale
|
||||
4. `openSingleTag()` ora chiama `showMassTagModal()` invece di mostrare la modale direttamente
|
||||
|
||||
## 2026-06-17 — Fix: Documenti — root mostra documenti di tutte le cartelle
|
||||
**Problema**: Alla root (`/documenti`), la query includeva tutti i documenti locali (`WHERE repository_id IS NULL`), mostrando documenti di tutte le cartelle in un unico elenco piatto.
|
||||
|
||||
**Fix** (`app/Http/Controllers/DocumentoController.php`):
|
||||
- Aggiunto `->whereNull('cartella_id')` alla query di root, così mostra solo documenti **senza cartella** (radice). Per vedere i documenti dentro una cartella bisogna navigarci dentro con `?folder_id=X`.
|
||||
|
||||
## 2026-06-17 — Fix: Documenti mass tag "nessun documento selezionato"
|
||||
**Problema**: Stesso identico bug di Eventi — toolbar button chiamava `$('#massTagModal').modal('show')` e il listener `show.bs.modal` non sempre popolava i campi.
|
||||
|
||||
**Fix** (`resources/views/documenti/index.blade.php`):
|
||||
1. Rimosso listener `show.bs.modal`
|
||||
2. Creata funzione `showMassTagModal()` (con fallback per `data-pre-selected`)
|
||||
3. `openSingleTag()` ora usa `showMassTagModal()` invece di mostrare la modale direttamente
|
||||
|
||||
## 2026-06-17 — Fix: ColumnManager pin colonne select/azioni con `cells[]` invece di `querySelector`
|
||||
**Problema**: Il pinning di `select` e `azioni` usava `row.querySelector('[data-column="..."]')` che funziona solo sul `<thead>` (dove `<th>` ha `data-column`), ma non sul `<tbody>` (dove i `<td>` non hanno `data-column`).
|
||||
|
||||
**Fix** (`public/js/column-manager.js`): Sostituito con `cells[keyToThIndex['select']]` che usa l'indice della colonna, valido sia per `th` che per `td`.
|
||||
|
||||
### DA FARE
|
||||
- Configurare `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` in `.env`
|
||||
- Test end-to-end: flusso OAuth completo (redirect → auth → callback → connessione creata)
|
||||
- Test XOAUTH2 SMTP: invio email via connessione OAuth
|
||||
- Test IMAP OAuth: sync email via connessione OAuth
|
||||
- Verificare refresh token automatico allo scadere
|
||||
- Verificare revoca e riconnessione
|
||||
|
||||
Reference in New Issue
Block a user