netgescon-day0/app/Http/Controllers/Filament/DocumentiPrintController.php

387 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Filament;
use App\Http\Controllers\Controller;
use App\Models\Documento;
use App\Models\DocumentoArchivioFisicoItem;
use App\Models\User;
use App\Services\Documenti\DocumentoArchivioVisibilityService;
use App\Support\StabileContext;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
class DocumentiPrintController extends Controller
{
public function download(Request $request, Documento $documento, DocumentoArchivioVisibilityService $visibilityService)
{
$user = Auth::user();
if (! $user instanceof User || ! $visibilityService->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,
);
}
}