individui e gruppi
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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).';
|
||||
|
||||
@@ -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.
Vendored
+1
-4
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+4
-4
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user