individui e gruppi

This commit is contained in:
2026-06-18 19:41:27 +02:00
parent d2084b9d07
commit baea181159
7 changed files with 109 additions and 25 deletions
+40
View File
@@ -25,6 +25,7 @@ App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
- **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)
- **2026-06-17**: Fix CalendarioConnessione — `encryptAndSetConfig()` perdeva campi sensibili in edit, `is_active` checkbox senza hidden fallback, query duplicata nella view
- **2026-06-18**: Fix import CSV — `declare(strict_types=1)` mancante in IndividuoController, header check assente, Log::warning() su errori; fix freeze server (set_time_limit, session_write_close, DB::transaction, fgetcsv length illimitato) in GruppoController e IndividuoController
## Funzionalità Implementate
@@ -665,3 +666,42 @@ Aggiunto `if (!Schema::hasTable('table_name')) { ... }` wrapper a 28 migration f
- PHP lint: OK su controller + routes
- JS brace balance: OK su index view
- Route list: `POST mailing-liste/mass-tag``MailingListController@massTag` (name: `mailing-liste.mass-tag`)
## 2026-06-18 — Fix import CSV: freeze server + bug IndividuoController
### Problema freeze server (Gruppi + Individui)
Importando CSV con molte righe, il server si bloccava:
1. **PHP max_execution_time (30s)** — killava il processo su import grandi
2. **Session lock** — altre richieste dello stesso utente restavano in attesa
3. **Nessuna transazione DB** — ogni `create()` era una INSERT individuale autocommit
4. **fgetcsv length=1000** — righe CSV più lunghe di 1000 caratteri venivano troncate
### Fix applicati a `GruppoController@importStore` e `IndividuoController@importStore`
```php
set_time_limit(0);
session_write_close();
$header = fgetcsv($handle, 0, ','); // length=0 = nessun limite
DB::beginTransaction();
// ... loop ...
DB::commit();
```
### Bug trovati in `IndividuoController@importStore`
| Bug | Fix |
|-----|-----|
| `declare(strict_types=1)` mancante | ✅ Aggiunto |
| Indentazione errata negli import (spazi extra su 8 righe) | ✅ Corretto |
| `use Illuminate\Support\Facades\Log` mancante | ✅ Aggiunto |
| Nessun controllo `$header === false` — CSV vuoto causava TypeError | ✅ Aggiunto |
| Nessun `array_map('trim', $header)` | ✅ Aggiunto |
| Nessun `Log::warning()` su eccezioni riga | ✅ Aggiunto |
| `fgetcsv` length=1000 (vs 0) | ✅ Portato a 0 |
| Nessun `set_time_limit(0)` / `session_write_close()` | ✅ Aggiunto |
| Nessun `DB::beginTransaction()` / `DB::commit()` | ✅ Aggiunto |
### File modificati
- `app/Http/Controllers/GruppoController.php` — aggiunti `use DB`, set_time_limit, session_write_close, fgetcsv length=0, DB::transaction
- `app/Http/Controllers/IndividuoController.php` — strict_types, Log import, DB import, header check, trim, set_time_limit, session_write_close, DB::transaction
### Verifica
- `php -l` su entrambi: nessun errore di sintassi
+10 -2
View File
@@ -11,6 +11,7 @@ use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class GruppoController extends Controller
@@ -385,9 +386,12 @@ class GruppoController extends Controller
'file' => 'required|file|mimes:csv,txt',
]);
set_time_limit(0);
session_write_close();
$file = $request->file('file');
$handle = fopen($file->path(), 'r');
$header = fgetcsv($handle, 1000, ',');
$header = fgetcsv($handle, 0, ',');
if ($header === false) {
fclose($handle);
@@ -400,7 +404,9 @@ class GruppoController extends Controller
$errors = [];
$rowNumber = 1;
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
DB::beginTransaction();
while (($row = fgetcsv($handle, 0, ',')) !== false) {
$rowNumber++;
if (count($row) < count($header)) {
@@ -444,6 +450,8 @@ class GruppoController extends Controller
fclose($handle);
DB::commit();
$message = 'Importati ' . $imported . ' gruppi.';
if ($skipped > 0) {
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
+29 -11
View File
@@ -1,19 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Individuo;
use App\Models\Contatto;
use App\Models\Documento;
use App\Models\Gruppo;
use App\Models\Comune;
use App\Models\Diocesi;
use App\Models\Tag;
use App\Models\VistaReport;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
use App\Models\Gruppo;
use App\Models\Comune;
use App\Models\Diocesi;
use App\Models\Tag;
use App\Models\VistaReport;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
class IndividuoController extends Controller
{
@@ -497,16 +501,27 @@ public function emailList(Request $request)
'file' => 'required|file|mimes:csv,txt',
]);
set_time_limit(0);
session_write_close();
$file = $request->file('file');
$handle = fopen($file->path(), 'r');
$header = fgetcsv($handle, 1000, ',');
$header = fgetcsv($handle, 0, ',');
if ($header === false) {
fclose($handle);
return back()->with('error', 'Il file CSV non è valido.');
}
$header = array_map('trim', $header);
$imported = 0;
$skipped = 0;
$errors = [];
$rowNumber = 1;
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
DB::beginTransaction();
while (($row = fgetcsv($handle, 0, ',')) !== false) {
$rowNumber++;
if (count($row) < count($header)) {
@@ -565,12 +580,15 @@ public function emailList(Request $request)
$imported++;
} catch (\Exception $e) {
Log::warning('Errore import individuo riga ' . $rowNumber . ': ' . $e->getMessage());
$errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage();
}
}
fclose($handle);
DB::commit();
$message = 'Importati ' . $imported . ' individui.';
if ($skipped > 0) {
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
Binary file not shown.
+1 -4
View File
@@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
+25 -4
View File
@@ -26,6 +26,12 @@ use Composer\Semver\VersionParser;
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
@@ -311,6 +317,18 @@ class InstalledVersions
self::$installedByVendor = array();
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
@@ -322,6 +340,7 @@ class InstalledVersions
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
@@ -330,9 +349,11 @@ class InstalledVersions
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $required;
$copiedLocalDir = true;
}
}
}
@@ -350,7 +371,7 @@ class InstalledVersions
}
}
if (self::$installed !== array()) {
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
+4 -4
View File
@@ -3,7 +3,7 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '92ba47e99f4839d49d5f002915c30a88bb842c6a',
'reference' => 'd2084b9d07cbeccb7e289e6f31ca767f6cce07c8',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -427,7 +427,7 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '92ba47e99f4839d49d5f002915c30a88bb842c6a',
'reference' => 'd2084b9d07cbeccb7e289e6f31ca767f6cce07c8',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -732,8 +732,8 @@
'dev_requirement' => false,
'provided' => array(
0 => '1.0 || 2.0 || 3.0',
1 => '3.0.0',
2 => '1.0|2.0|3.0',
1 => '1.0|2.0|3.0',
2 => '3.0.0',
),
),
'psr/simple-cache' => array(