stateService->initialize($importId, count($storedFiles)); $this->stateService->appendLog($importId, $initialLog); ImportTerritoryPdfFolder::dispatch($importId, $storedFiles, $actorId); return $this->stateService->get($importId) ?? $state; } public function dispatchUploadedZip(UploadedFile $zipFile, ?int $actorId): string { $importId = (string) Str::uuid(); $zipStoredPath = $zipFile->storeAs( 'bulk-territori-imports/' . $importId, 'archivio-' . $importId . '.zip', 'local' ); $zipAbsolutePath = storage_path('app/' . $zipStoredPath); $zip = new ZipArchive(); if ($zip->open($zipAbsolutePath) !== true) { throw new RuntimeException('Impossibile aprire il file ZIP.'); } $storedFiles = []; $entryIndex = 0; try { for ($index = 0; $index < $zip->numFiles; $index++) { $entryName = $zip->getNameIndex($index); if (! $entryName || str_ends_with($entryName, '/')) { continue; } if (strtolower(pathinfo($entryName, PATHINFO_EXTENSION)) !== 'pdf') { continue; } if ($entryIndex >= self::MAX_ENTRIES) { throw new RuntimeException( 'Lo ZIP contiene più di ' . self::MAX_ENTRIES . ' PDF. Suddividilo in archivi più piccoli.' ); } // Check the *uncompressed* size before reading. getFromIndex() // inflates the entry entirely into memory, so a small zip with // one hugely compressible entry would otherwise exhaust PHP's // memory limit and abort the request mid-extraction. $uncompressedSize = $zip->statIndex($index)['size'] ?? 0; if ($uncompressedSize > self::MAX_ENTRY_BYTES) { throw new RuntimeException(sprintf( 'Il file "%s" nello ZIP supera %d MB.', basename($entryName), self::MAX_ENTRY_BYTES / 1048576 )); } $content = $zip->getFromIndex($index); if ($content === false) { continue; } $originalName = basename($entryName); $safeName = Str::slug(pathinfo($originalName, PATHINFO_FILENAME)); $storedPath = 'bulk-territori-imports/' . $importId . '/zip-' . str_pad((string) $entryIndex, 4, '0', STR_PAD_LEFT) . '-' . $safeName . '.pdf'; file_put_contents(storage_path('app/' . $storedPath), $content); $storedFiles[] = [ 'original_name' => $originalName, 'stored_path' => $storedPath, ]; $entryIndex++; } } catch (\Throwable $exception) { // Nothing was queued, so the job's own cleanup will never run — // drop the partially extracted files rather than orphaning them. Storage::disk('local')->deleteDirectory('bulk-territori-imports/' . $importId); throw $exception; } finally { $zip->close(); } if ($storedFiles === []) { throw new RuntimeException('Lo ZIP non contiene file PDF validi.'); } $this->dispatchStoredFiles( $importId, $storedFiles, $actorId, 'Archivio ZIP ricevuto: ' . count($storedFiles) . ' PDF estratti e messi in coda per l\'elaborazione.' ); return $importId; } }