canAccessDocumento($user, $documento)) { abort(403); } $pathCandidates = []; if (is_string($documento->percorso_file) && trim($documento->percorso_file) !== '') { $pathCandidates[] = trim($documento->percorso_file); } if (is_string($documento->path_file) && trim($documento->path_file) !== '') { $pathCandidates[] = trim($documento->path_file); } $selectedDisk = null; $selectedPath = null; foreach ($pathCandidates as $candidate) { if (Storage::disk('local')->exists($candidate)) { $selectedDisk = 'local'; $selectedPath = $candidate; break; } if (Storage::disk('public')->exists($candidate)) { $selectedDisk = 'public'; $selectedPath = $candidate; break; } } if (! $selectedDisk || ! $selectedPath) { abort(404); } $filename = is_string($documento->nome_file) && trim($documento->nome_file) !== '' ? trim($documento->nome_file) : ('documento-' . (int) $documento->id . '.pdf'); $filename = str_replace(["\r", "\n", '"'], '', $filename); $absolutePath = Storage::disk($selectedDisk)->path($selectedPath); if ($request->boolean('watermark')) { $watermarkedPath = $this->buildWatermarkedPdf($absolutePath, $documento); if (is_string($watermarkedPath) && is_file($watermarkedPath)) { $response = response()->download($watermarkedPath, $filename); if ($request->boolean('inline')) { $response->setContentDisposition('inline', $filename); } return $response->deleteFileAfterSend(true); } } if ($request->boolean('inline')) { return response()->file($absolutePath, [ 'Content-Disposition' => 'inline; filename="' . $filename . '"', ]); } return Storage::disk($selectedDisk)->download($selectedPath, $filename); } public function etichetta(Request $request, Documento $documento, DocumentoArchivioVisibilityService $visibilityService) { $user = Auth::user(); if (! $user instanceof User || ! $visibilityService->canAccessDocumento($user, $documento)) { abort(403); } $format = $request->query('format', '11354'); $format = in_array($format, ['11354', '99014'], true) ? $format : '11354'; $defaults = $this->getDymoDefaultsForUser($format); // Alcuni driver DYMO ruotano automaticamente la 11354: di default attiviamo un fix. $dymoFix = $request->query('dymo_fix'); if ($dymoFix === null) { // Default: stampa orizzontale corretta (57x32). Abilita fix solo se necessario. $dymoFix = (bool) ($defaults['fix'] ?? false); } else { $dymoFix = filter_var($dymoFix, FILTER_VALIDATE_BOOL); } $dymoRot = $request->has('dymo_rot') ? (int) $request->query('dymo_rot') : (int) ($defaults['rot'] ?? 0); // rot serve solo per la modalità fix (workaround DYMO 11354). In modalità normale lo ignoriamo. if ($format === '11354' && $dymoFix) { if (! in_array($dymoRot, [-90, 90], true)) { $dymoRot = -90; } } else { // Normalizza a valori “sensati” (multipli di 90) per evitare input casuali. if (! in_array($dymoRot, [-180, -90, 0, 90, 180], true)) { $dymoRot = 0; } } $dymoDx = $request->has('dymo_dx') ? (float) $request->query('dymo_dx') : (float) ($defaults['dx'] ?? 0); $dymoDy = $request->has('dymo_dy') ? (float) $request->query('dymo_dy') : (float) ($defaults['dy'] ?? 0); // Clamp: evita valori assurdi che fanno “sparire” l'etichetta. $dymoDx = max(-15.0, min(15.0, $dymoDx)); $dymoDy = max(-15.0, min(15.0, $dymoDy)); $faldone = trim((string) $request->query('faldone', '')); if ($faldone === '') { $faldone = $this->extractFaldoneFromNote((string) ($documento->note ?? '')); } $anno = $documento->data_documento?->format('Y') ?: now()->format('Y'); $protocollo = (string) ($documento->numero_protocollo ?: ('DOC-' . $anno . '-' . str_pad((string) $documento->id, 3, '0', STR_PAD_LEFT))); $stabileId = (int) ($documento->stabile_id ?: 0); $codiceUnico = $stabileId > 0 ? ('STB-' . $stabileId . '-' . $protocollo) : $protocollo; if ($faldone !== '') { $codiceUnico .= '-F' . Str::upper($faldone); } $openUrl = route('filament.documenti.download', $documento); return view('filament.print.etichetta-documento', [ 'documento' => $documento, 'format' => $format, 'dymoFix' => $dymoFix, 'dymoRot' => $dymoRot, 'dymoDx' => $dymoDx, 'dymoDy' => $dymoDy, 'faldone' => $faldone, 'codiceUnico' => $codiceUnico, 'openUrl' => $openUrl, 'anno' => $anno, 'protocollo' => $protocollo, ]); } public function etichettaArchivioFisico(Request $request, DocumentoArchivioFisicoItem $item) { $user = Auth::user(); if (! $user instanceof User || ! $this->canAccessPhysicalArchiveItem($user, $item)) { abort(403); } $format = $request->query('format', '11354'); $format = in_array($format, ['11354', '99014'], true) ? $format : '11354'; $labelPrinterMode = $request->boolean('autoprint') || $request->has('dymo_fix') || $request->has('dymo_rot') || $request->has('dymo_dx') || $request->has('dymo_dy'); $defaults = $labelPrinterMode ? $this->getDymoDefaultsForUser($format) : [ 'fix' => false, 'rot' => 0, 'dx' => 0.0, 'dy' => 0.0, ]; $dymoFix = $request->query('dymo_fix'); if ($dymoFix === null) { $dymoFix = (bool) ($defaults['fix'] ?? false); } else { $dymoFix = filter_var($dymoFix, FILTER_VALIDATE_BOOL); } $dymoRot = $request->has('dymo_rot') ? (int) $request->query('dymo_rot') : (int) ($defaults['rot'] ?? 0); $dymoDx = $request->has('dymo_dx') ? (float) $request->query('dymo_dx') : (float) ($defaults['dx'] ?? 0); $dymoDy = $request->has('dymo_dy') ? (float) $request->query('dymo_dy') : (float) ($defaults['dy'] ?? 0); if ($format === '11354' && $dymoFix) { if (! in_array($dymoRot, [-90, 90], true)) { $dymoRot = -90; } } else { if (! in_array($dymoRot, [-180, -90, 0, 90, 180], true)) { $dymoRot = 0; } } $dymoDx = max(-15.0, min(15.0, $dymoDx)); $dymoDy = max(-15.0, min(15.0, $dymoDy)); return view('filament.print.etichetta-archivio-fisico', [ 'item' => $item->load(['parent', 'children', 'stabile', 'documento']), 'format' => $format, 'labelPrinterMode' => $labelPrinterMode, 'dymoFix' => $dymoFix, 'dymoRot' => $dymoRot, 'dymoDx' => $dymoDx, 'dymoDy' => $dymoDy, 'publicUrl' => $item->buildPublicQrUrl(), ]); } public function showPublicArchivioFisicoItem(Request $request, DocumentoArchivioFisicoItem $item) { if (! $request->hasValidSignature(false)) { abort(403); } $chain = collect(); $current = $item; while ($current instanceof DocumentoArchivioFisicoItem) { $chain->prepend($current); $current = $current->parent; } return view('filament.pages.strumenti.archivio-fisico-show', [ 'item' => $item->load(['children.children', 'stabile', 'documento', 'parent']), 'chain' => $chain, ]); } private function getDymoDefaultsForUser(string $format): array { $user = Auth::user(); if (! $user instanceof User) { return []; } $imp = is_array($user->amministratore?->impostazioni ?? null) ? $user->amministratore->impostazioni : []; $key = 'stampe.dymo.' . $format; $cfg = Arr::get($imp, $key); if (! is_array($cfg)) { // Default operativi (senza configurazione salvata): // - 11354 (57x32): nel setup corrente risulta stabile con rot=0, dx=0, dy=3, fix=0 // - 99014: nessun offset di default if ($format === '11354') { return [ 'fix' => false, 'rot' => 0, 'dx' => 0.0, 'dy' => 3.0, ]; } return [ 'fix' => false, 'rot' => 0, 'dx' => 0.0, 'dy' => 0.0, ]; } return [ 'fix' => (bool) ($cfg['fix'] ?? false), 'rot' => isset($cfg['rot']) ? (int) $cfg['rot'] : null, 'dx' => isset($cfg['dx']) ? (float) $cfg['dx'] : 0, 'dy' => isset($cfg['dy']) ? (float) $cfg['dy'] : 0, ]; } private function extractFaldoneFromNote(string $note): string { $note = trim($note); if ($note === '') { return ''; } // Supporta "Faldone: XYZ" if (preg_match('/faldone\s*:\s*([^\n\r]+)/i', $note, $m)) { return trim((string) ($m[1] ?? '')); } return ''; } private function canAccessPhysicalArchiveItem(User $user, DocumentoArchivioFisicoItem $item): bool { if ($user->hasAnyRole(['super-admin', 'admin'])) { return true; } return StabileContext::accessibleStabili($user) ->pluck('id') ->map(fn($value): int => (int) $value) ->contains((int) $item->stabile_id); } private function buildWatermarkedPdf(string $inputPath, Documento $documento): ?string { $ghostscript = trim((string) shell_exec('command -v gs 2>/dev/null')); if ($ghostscript === '' || ! is_file($inputPath)) { return null; } $watermarkText = $this->buildWatermarkText($documento); if ($watermarkText === '') { return null; } $outputPath = tempnam(sys_get_temp_dir(), 'doc-wm-'); if (! is_string($outputPath) || $outputPath === '') { return null; } $pdfOutputPath = $outputPath . '.pdf'; @unlink($outputPath); $postScript = '<< /BeginPage { gsave 0.90 setgray /Helvetica-Bold findfont 14 scalefont setfont 140 90 translate 38 rotate 0 0 moveto (' . $this->escapePostScriptString($watermarkText) . ') show grestore } >> setpagedevice'; $process = new Process([ $ghostscript, '-q', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pdfwrite', '-dCompatibilityLevel=1.4', '-o', $pdfOutputPath, '-c', $postScript, '-f', $inputPath, ]); $process->run(); if (! $process->isSuccessful() || ! is_file($pdfOutputPath)) { @unlink($pdfOutputPath); return null; } return $pdfOutputPath; } private function buildWatermarkText(Documento $documento): string { $archiveReference = trim((string) ($documento->archive_reference ?? '')); $meta = is_array($documento->metadati_archivio ?? null) ? $documento->metadati_archivio : []; $physicalParts = array_filter([ trim((string) ($meta['supporto_fisico'] ?? '')), trim((string) ($meta['contenitore_numero'] ?? '')), trim((string) ($meta['busta_numero'] ?? '')) !== '' ? 'B' . trim((string) $meta['busta_numero']) : null, trim((string) ($meta['fascicolo_numero'] ?? '')) !== '' ? 'F' . trim((string) $meta['fascicolo_numero']) : null, ]); $physicalReference = implode(' ', $physicalParts); return trim(implode(' | ', array_filter([ $archiveReference !== '' ? 'DIG ' . $archiveReference : null, $physicalReference !== '' ? 'FIS ' . $physicalReference : null, ]))); } private function escapePostScriptString(string $value): string { $ascii = (string) Str::of($value)->ascii()->replaceMatches('/\s+/', ' ')->trim(); return str_replace( ['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $ascii, ); } }