From cbe3470c8c6202f5f7091777692490fb074b0af9 Mon Sep 17 00:00:00 2001 From: benzimariano Date: Mon, 8 Jun 2026 09:47:24 +0200 Subject: [PATCH] 1.2.5 - fix for email settings --- MEMORY.md | 133 ++++++++------ app/Console/Commands/DiagnoseEmail.php | 167 ++++++++++++++++++ build-dist.sh | 39 +++- .../admin/email-settings/index.blade.php | 15 +- resources/views/impostazioni/index.blade.php | 15 +- storage/framework/views/.gitignore | 2 + vendor/composer/installed.php | 4 +- 7 files changed, 300 insertions(+), 75 deletions(-) create mode 100644 app/Console/Commands/DiagnoseEmail.php create mode 100644 storage/framework/views/.gitignore diff --git a/MEMORY.md b/MEMORY.md index 19980fbd..b82d0870 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -1,70 +1,97 @@ # 🧠 MEMORY.md — Stato Progetto -## Goal -- Implementare page-length selector su liste (Individui, Eventi, Email) -- Implementare CSV import per gruppi -- Implementare ICS import per eventi -- Creare script build-dist.sh per distribuzione +## Obiettivo +App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi. -## Modifiche Effettuate (10 file + 3 nuovi) +## Funzionalità Implementate -### File modificati -| File | Cosa | -|------|------| -| `app/Http/Controllers/IndividuoController.php` | perPage support (20 default, clamp [10,20,25,50,100]) | -| `app/Http/Controllers/EventoController.php` | perPage + importIcs/importIcsStore (Sabre\VObject) | -| `app/Http/Controllers/GruppoController.php` | import/importStore/downloadTemplate CSV gruppi | -| `app/Http/Controllers/EmailController.php` | perPage support | -| `app/Models/Evento.php` | `uid_esterno` aggiunto a $fillable (bug fix) | -| `resources/views/individui/index.blade.php` | dropdown perPage in card-footer | -| `resources/views/eventi/index.blade.php` | dropdown perPage + pulsante Importa ICS | -| `resources/views/gruppi/index.blade.php` | pulsante Importa CSV | -| `resources/views/email/index.blade.php` | dropdown perPage in card-footer | -| `routes/web.php` | route gruppi/import, gruppi/template, eventi/import | - -### File nuovi -| File | Cosa | -|------|------| -| `resources/views/gruppi/import.blade.php` | form upload CSV gruppi | -| `resources/views/eventi/import.blade.php` | form upload ICS eventi | -| `build-dist.sh` | script build distribuzione | - -## Feature - -### 1. Page-length selector -- **3 pagine**: Individui, Eventi, Email +### Page-length selector +- **4 pagine**: Individui, Eventi, Email, Gruppi - **Valori**: 10, 20, 25, 50, 100 (default 20) -- Dropdown nel card-footer con hidden fields per preservare altri query param +- Dropdown nel card-footer con campi hidden per preservare query params +- GruppoController: usa `LengthAwarePaginator` per paginare la collezione ordinata gerarchicamente -### 2. CSV Import Gruppi +### CSV Import Gruppi - Stesso pattern di `IndividuoController` (import GET, importStore POST, downloadTemplate GET) - Colonne: nome, descrizione, parent_id, diocesi_id, indirizzo_incontro, cap_incontro, città_incontro, sigla_provincia_incontro -- Fault-tolerant: skip righe vuote, log errori +- Fault-tolerant: skip righe vuote, log errori, try/catch su ogni riga -### 3. ICS Import Eventi +### ICS Import Eventi - Usa `Sabre\VObject\Reader::read()` già in vendor/ - Mapping: SUMMARY→nome_evento, DESCRIPTION→descrizione, DTSTART→data_specifica+ora_inizio, DTEND→durata_minuti, LOCATION→luogo_indirizzo, UID→uid_esterno - Dedup via uid_esterno - RRULE: import come evento singolo con prima occorrenza -### 4. Script build-dist.sh -- Genera `glastree-YYYYMMDD_HHMM.tar.gz` (~26MB) -- Include: sorgenti, vendor (production), Dockerfile, docker-compose, install.php, install.sql -- Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests +### build-dist.sh (script di distribuzione) +- Genera `glastree-YYYYMMDD_HHMM.tar.gz` +- Include: sorgenti, vendor (production) +- Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker +- **Istruzioni post-estrazione complete**: mkdir, permessi, configurazione, cache clear -## Installazione su server remoto +## Bug Fix Recenti + +### 2026-06-07 — Colonne mancanti in eventi e gruppo_individuo +- `descrizione_evento` e `is_incontro_gruppo` mancanti in install.sql → creata migration + ALTER TABLE +- `ruolo_nel_gruppo` mancante in install.sql per pivot `gruppo_individuo` +- `VistaReport.php`: aggiunto `is_default` a `$fillable` + +### 2026-06-08 — build-dist.sh: "provide valid cache path" su server remoto +**Problema**: Le esclusioni in `build-dist.sh` usavano `--exclude='storage/framework/cache'` (e simili per sessions, views, logs), che escludevano **l'intera directory** dall'archivio. Dopo l'estrazione, le directory necessarie a Laravel non esistevano. + +**Fix**: +1. Cambiati exclude patterns per **contenuti soltanto** (non la directory): + - `--exclude='storage/framework/cache/*'` (non `/cache`) + - `--exclude='storage/framework/sessions/*'` + - `--exclude='storage/framework/views/*'` + - `--exclude='storage/logs/*'` + - `--exclude='storage/debugbar/*'` +2. Aggiunte esclusioni per upload utente: + - `--exclude='storage/app/public'` + - `--exclude='storage/app/private'` +3. Creato `.gitignore` in `storage/framework/views/` (mancante) +4. Istruzioni post-estrazione riscritte con: + - `mkdir -p` per tutte le directory necessarie (cache, sessions, views, logs, public, backups, documenti, bootstrap/cache) + - `chmod -R 775` e `chown -R www-data:www-data` per permessi + - `php artisan config:clear`, `route:clear`, `view:clear` + +## Problema 419 (Page Expired) su Produzione +Il codice NON ha errori di CSRF — tutte le form hanno `@csrf`. Il problema è configurazione sessione del server: +1. `storage/framework/sessions/` non scrivibile dal web server +2. `APP_URL` in `.env` non corrisponde all'URL reale +3. `SESSION_SECURE_COOKIE=true` ma sito in HTTP + +Soluzione: ```bash -# Estrarre -tar xzf glastree-20260607_2052.tar.gz - -# Configurare -cp .env.example .env -php artisan key:generate -php artisan storage:link - -# Opzione A — Docker -docker compose up -d - -# Opzione B — Apache nativo (MySQL) -php install.php +chmod -R 775 storage/framework/sessions +php artisan config:clear ``` + +### 2026-06-08 — build-dist.sh: impossibilità salvare logo su server remoto +**Problema**: +- `public/storage` era un symlink **assoluto** (`/var/www/html/glastree/storage/app/public`) incluso nell'archivio → all'estrazione su altro server puntava a path inesistente. +- `php artisan storage:link` falliva perché il symlink rotto esisteva già. +- Sottodirectory `logos/`, `documenti/eventi/`, `avatars/gruppi/` non create dal `mkdir -p`. + +**Fix**: +1. Aggiunto `--exclude='public/storage'` al tar — il symlink va ricreato sul target. +2. Aggiunto `rm -f public/storage` prima di `php artisan storage:link` per evitare conflitto. +3. Usato `ln -sf ../storage/app/public public/storage` (invece di `php artisan storage:link --relative` che richiede `symfony/filesystem` non sempre presente). +4. Aggiunte al `mkdir -p` le sottodirectory mancanti: + - `storage/app/public/logos` (logo upload) + - `storage/app/public/documenti/eventi` (event documenti) + - `storage/app/private/avatars/gruppi` (avatar gruppi) +5. Aggiunta creazione `.gitignore` in `storage/app/public/` (placeholder Laravel). + +### Fix form annidato salvataggio email +**2026-06-08 — Problema**: Il form di DELETE era **annidato dentro** il form di SAVE in entrambe le pagine: +- `resources/views/impostazioni/index.blade.php` +- `resources/views/admin/email-settings/index.blade.php` +- HTML invalido: il browser chiudeva implicitamente il form SAVE all'apertura del form DELETE, causando l'invio alla route sbagliata. + +**Fix**: Spostato il form DELETE fuori dal form SAVE, separato in un `div` autonomo. + +### DiagnoseEmail Command +- `php artisan diagnose:email` — controlla APP_KEY, tabella email_settings, record, decrittazione password, sender_accounts, log errori, estensioni PHP. + +## Prossimi Passi +- *(nessuno — in attesa di nuove richieste)* diff --git a/app/Console/Commands/DiagnoseEmail.php b/app/Console/Commands/DiagnoseEmail.php new file mode 100644 index 00000000..e499a6cf --- /dev/null +++ b/app/Console/Commands/DiagnoseEmail.php @@ -0,0 +1,167 @@ +info('=== Diagnostica Email Settings ==='); + $this->newLine(); + + // 1. APP_KEY + $this->section('APP_KEY'); + $appKey = config('app.key'); + if ($appKey && str_starts_with($appKey, 'base64:')) { + $this->line('APP_KEY: presente e valida ✓'); + } elseif ($appKey) { + $this->warn('APP_KEY: presente ma non in formato base64 (potrebbe funzionare lo stesso)'); + } else { + $this->error('APP_KEY: NON IMPOSTATA — crittografia password non funzionerà!'); + } + + // 2. Tabella email_settings + $this->newLine(); + $this->section('Tabella email_settings'); + if (Schema::hasTable('email_settings')) { + $this->line('Tabella email_settings: esiste ✓'); + $columns = Schema::getColumnListing('email_settings'); + $this->line('Colonne: ' . implode(', ', $columns)); + + $required = ['imap_host', 'imap_username', 'imap_password', 'email_address']; + $missing = array_diff($required, $columns); + if ($missing) { + $this->error('Colonne mancanti: ' . implode(', ', $missing)); + } else { + $this->line('Colonne richieste presenti ✓'); + } + } else { + $this->error('Tabella email_settings NON ESISTE! Esegui: php artisan migrate'); + } + + // 3. Record email_settings + $this->newLine(); + $this->section('Record email_settings'); + $settings = EmailSetting::first(); + if ($settings) { + $this->line('Record trovato (ID: ' . $settings->id . ')'); + $this->line(' imap_host: ' . ($settings->imap_host ?: 'VUOTO')); + $this->line(' imap_username: ' . ($settings->imap_username ?: 'VUOTO')); + $this->line(' imap_password: ' . ($settings->imap_password ? '[CRITTATO]' : 'VUOTO')); + $this->line(' email_address: ' . ($settings->email_address ?: 'VUOTO')); + $this->line(' smtp_host: ' . ($settings->smtp_host ?: 'VUOTO')); + + // Prova decrittazione + if ($settings->imap_password) { + try { + $decrypted = Crypt::decryptString($settings->imap_password); + $this->line(' -> password IMAP decrittata: ' . (strlen($decrypted) > 0 ? 'OK (' . strlen($decrypted) . ' caratteri)' : 'VUOTA')); + } catch (\Exception $e) { + $this->error(' -> ERRORE decrittazione password IMAP: ' . $e->getMessage()); + $this->warn(' Causa probabile: APP_KEY diversa da quando è stata salvata.'); + } + } + } else { + $this->warn('NESSUN record in email_settings — nessuna configurazione salvata.'); + $this->line('Vai su /impostazioni#email-imap, compila e clicca "Salva Impostazioni Email"'); + } + + // 4. Tabella sender_accounts + $this->newLine(); + $this->section('Tabella sender_accounts'); + if (Schema::hasTable('sender_accounts')) { + $this->line('Tabella sender_accounts: esiste ✓'); + $count = DB::table('sender_accounts')->count(); + $this->line('Record presenti: ' . $count); + } else { + $this->error('Tabella sender_accounts NON ESISTE!'); + } + + // 5. Log recenti + $this->newLine(); + $this->section('Errori recenti (storage/logs/laravel.log)'); + $logPath = storage_path('logs/laravel.log'); + if (file_exists($logPath)) { + $lines = $this->getLastErrorLines($logPath, 20); + if ($lines) { + foreach ($lines as $line) { + $this->line($line); + } + } else { + $this->line('Nessun errore recente trovato.'); + } + } else { + $this->warn('File di log non trovato: ' . $logPath); + } + + // 6. Estensioni PHP + $this->newLine(); + $this->section('Estensioni PHP'); + $requiredExts = ['openssl', 'json', 'mbstring', 'pdo', 'fileinfo']; + foreach ($requiredExts as $ext) { + if (extension_loaded($ext)) { + $this->line($ext . ': OK ✓'); + } else { + $this->error($ext . ': MANCANTE!'); + } + } + + $this->newLine(); + $this->info('=== Diagnostica completata ==='); + + return self::SUCCESS; + } + + private function section(string $title): void + { + $this->line("--- {$title} ---"); + } + + private function getLastErrorLines(string $path, int $maxLines): array + { + $lines = []; + $fp = fopen($path, 'r'); + if (!$fp) { + return $lines; + } + + fseek($fp, -1, SEEK_END); + $pos = ftell($fp); + $buffer = ''; + + while ($pos > 0 && count($lines) < $maxLines) { + $char = fgetc($fp); + if ($char === "\n") { + if (trim($buffer) !== '') { + $lines[] = trim($buffer); + } + $buffer = ''; + } else { + $buffer = $char . $buffer; + } + $pos--; + fseek($fp, $pos); + } + + if (trim($buffer) !== '') { + $lines[] = trim($buffer); + } + + fclose($fp); + + return $lines; + } +} diff --git a/build-dist.sh b/build-dist.sh index d2a88b79..14771321 100755 --- a/build-dist.sh +++ b/build-dist.sh @@ -37,11 +37,14 @@ tar czf "${ARCHIVE}" \ --exclude='vendor/*/doc' \ --exclude='storage/app/backups' \ --exclude='storage/app/documenti' \ - --exclude='storage/framework/cache' \ - --exclude='storage/framework/sessions' \ - --exclude='storage/framework/views' \ - --exclude='storage/logs' \ - --exclude='storage/debugbar' \ + --exclude='storage/app/public' \ + --exclude='storage/app/private' \ + --exclude='public/storage' \ + --exclude='storage/framework/cache/*' \ + --exclude='storage/framework/sessions/*' \ + --exclude='storage/framework/views/*' \ + --exclude='storage/logs/*' \ + --exclude='storage/debugbar/*' \ --exclude='bootstrap/cache/*.php' \ --exclude='public/hot' \ --exclude='*.tar.gz' \ @@ -67,7 +70,27 @@ echo "Per estrarre sul server di destinazione:" echo " tar xzf ${ARCHIVE}" echo "" echo "Poi esegui:" -echo " cp .env.example .env # configura il tuo ambiente" +echo " # 1. Crea directory necessarie (se non esistono già)" +echo ' mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views storage/logs bootstrap/cache storage/app/public storage/app/public/logos storage/app/public/documenti/eventi storage/app/backups storage/app/documenti storage/app/private/avatars/gruppi' +echo "" +echo " # 2. Crea file .gitignore in storage/app/public (serve a Laravel)" +echo ' echo "*" > storage/app/public/.gitignore' +echo ' echo "!.gitignore" >> storage/app/public/.gitignore' +echo "" +echo " # 3. Permessi (web server = www-data)" +echo ' chmod -R 775 storage bootstrap/cache' +echo ' chown -R www-data:www-data storage bootstrap/cache' +echo "" +echo " # 4. Configura ambiente" +echo " cp .env.example .env # modifica DB, APP_URL, etc." echo " php artisan key:generate" -echo " php artisan storage:link" -echo " php install.php # avvia installazione MySQL" +echo "" +echo " # 5. Pulisci cache e ricrea symlink storage (relativo per portabilità)" +echo ' rm -f public/storage' +echo ' ln -sf ../storage/app/public public/storage' +echo " php artisan config:clear" +echo " php artisan route:clear" +echo " php artisan view:clear" +echo "" +echo " # 6. Avvia installazione MySQL" +echo " php install.php" diff --git a/resources/views/admin/email-settings/index.blade.php b/resources/views/admin/email-settings/index.blade.php index 9e39ec17..2b929fc1 100644 --- a/resources/views/admin/email-settings/index.blade.php +++ b/resources/views/admin/email-settings/index.blade.php @@ -390,14 +390,17 @@ Sincronizza Ora -
- @csrf @method('DELETE') - -
+ +
+
+ @csrf @method('DELETE') + +
+
@endsection diff --git a/resources/views/impostazioni/index.blade.php b/resources/views/impostazioni/index.blade.php index efacb352..4b7b3ef3 100644 --- a/resources/views/impostazioni/index.blade.php +++ b/resources/views/impostazioni/index.blade.php @@ -722,14 +722,17 @@ Sincronizza Ora -
- @csrf @method('DELETE') - -
+ +
+
+ @csrf @method('DELETE') + +
+
{{-- === CALENDARIO === --}} diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index ad821a0a..7f2a72d3 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'laravel/laravel', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'f9c1268466e9f534c2f1ade6e5bb4bddfbaa56e5', + 'reference' => '469e8c014fba82f598245b5a76fba8e1209828c8', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -427,7 +427,7 @@ 'laravel/laravel' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'f9c1268466e9f534c2f1ade6e5bb4bddfbaa56e5', + 'reference' => '469e8c014fba82f598245b5a76fba8e1209828c8', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),