Cosa fa questo modulo
Modulo «Gallery immagini con upload S3/locale»: scheda inventario del layer media-management generico basato su `spatie/laravel-medialibrary` v11 (standard ecosistema Laravel) per gestire upload, storage, conversioni e delivery di immagini su qualunque entità del dominio (Pagina, Articolo, Prodotto, Servizio, Stanza, Veicolo, Patient, Documento, User profile, ecc.) in modo polimorfico e riutilizzabile. Architettura «storage-agnostic»: ogni cliente sceglie via `.env` MEDIA_DISK il backend (`s3`/`s3-cloudflare-r2`/`s3-backblaze-b2`/`s3-wasabi`/`s3-ovh`/`public` locale Laravel `storage/app/public` con symlink, `digital_ocean_spaces`); switch trasparente al codice applicativo — modulo non assume mai S3 hardcoded. Tabella standard `media` di spatie (model_type/model_id polimorfico, uuid, collection_name per separare gallerie multiple sullo stesso modello — es. `gallery`/`cover`/`attachments`/`certificates`, name, file_name, mime_type, disk, conversions_disk, size bytes, manipulations JSON, custom_properties JSON con campi cliente come alt_text/caption/credit/license, generated_conversions JSON con flag boolean per ogni conversion generata, responsive_images JSON con breakpoints, order_column per drag&drop). Conversioni di default registrate via `registerMediaConversions` su trait `InteractsWithMedia`: `thumb` 200x200 fit crop center, `medium` 600x600 fit contain, `large` 1600w fit contain, `webp` per tutti i formati (compression 80), `avif` per browser moderni (compression 60 next-gen), `og` 1200x630 fit crop center per Open Graph (#8311), `print` 300dpi opt. per stampa cataloghi. Pipeline conversion: queue-driven (job `PerformConversionsJob` su connection `media` configurabile), non-blocking l'upload sincrono (utente vede placeholder + conversion in background), retry on failure 3x, dead-letter notification admin se conversione fallisce (es. ImageMagick OOM su file 50MP). Responsive images: spatie `withResponsiveImages()` genera N width breakpoints (320/640/960/1280/1920/2560) + Tiny Placeholder SVG inline base64 (BlurHash o ThumbHash più efficienti, opt.) per LQIP preview anti-CLS, output `srcset`/`sizes` via helper `<x-gallery-image :media="$photo" sizes="(max-width: 768px) 100vw, 50vw" />` con `loading="lazy"` + `decoding="async"` + `fetchpriority` automatico (first hero `high`, resto `low`), `<picture>` element con sources AVIF→WebP→JPEG fallback. Upload UI: componente Livewire/Vue/Alpine.js `<gallery-uploader>` con drag&drop area (HTML5 `dragenter`/`drop`), file picker multi-select, preview pre-upload con cropper opz. (Cropper.js per crop manuale 16:9/4:3/1:1/free), progress bar per file con XHR upload, parallel upload N=3 simultanei, retry automatico on network failure, validazione client-side (mime allowlist `image/jpeg|png|webp|avif|heic`, size max 10MB configurabile, dimensioni min/max), conversione client-side opzionale HEIC→JPEG via browser-image-compression (iPhone foto upload da safari), error UI per file rifiutato con motivo, deduplicazione hash MD5 (no double-upload stesso file in stessa collection). Ordinamento gallery via Sortable.js drag&drop con persistenza `order_column` PATCH /api/media/{id}/order, rinumerazione automatica gap-free. Eliminazione: trash logico opt. (custom_properties trashed_at) con `media:purge-trash` cron 30gg + hard delete con conferma admin, restore da trash, audit log azione (#8330). Edit metadata in modal: alt_text per accessibilità SEO obbligatorio (warning admin se assente, blocca pubblicazione opz.), caption visibile lightbox, credit fotografo per copyright, license enum (`all_rights_reserved`/`cc_by`/`cc_by_sa`/`cc_zero`/`royalty_free_stock`/`client_owned`), tags JSON per filtri, geolocation lat/lng opt. (EXIF GPS o manuale), shot_at timestamp (EXIF DateTimeOriginal o manuale). Auto-extract EXIF al upload: dimensioni, camera_make/model, iso/aperture/shutter/focal_length, gps, orientation (auto-rotate via Intervention Image), profilo colore (sRGB conversion forzata per web). Watermark opzionale: stamp logo angolo basso destra (10% width, opacity 50%) applicato come conversion separata `_wm` solo su delivery pubblica, originale preservato; pattern uso: e-commerce/foto stock/agenzia immobiliare. Signed URL per asset privati: collection `private` su disco `s3` con visibility `private` (ACL non-public), delivery via route firmata `GET /media/{uuid}/signed?expires=…&signature=…` (Laravel `URL::temporarySignedRoute`) con TTL 1h default, casi d'uso: documenti caricati area cliente (#8326), allegati ticket sanitario the-body-code, fatture PDF (#8322 — anche se PDF non immagine, modulo gestisce attachments generici via collection `documents`). CDN delivery: header `Cache-Control: public, max-age=31536000, immutable` per asset con hash in filename, integrazione Cloudflare/BunnyCDN/CloudFront via base URL configurabile `MEDIA_CDN_URL`, purge on delete via webhook API (Cloudflare API token). Browser image format negotiation: middleware `Accept` header check → serve AVIF se browser supporta (Chrome 85+/Firefox 93+/Safari 16+), WebP fallback, JPEG legacy last-resort; Cloudflare Image Resizing opt. (più costoso, evita rigenerazione lato server). Lightbox UI front-end: componente Vue/Alpine `<gallery-lightbox>` con swipe touch mobile (Hammer.js o nativo), keyboard nav (←/→/Esc), zoom pinch, lazy-load slides successive, deep-link `#photo-{id}` per condivisione singolo scatto, Open Graph share button (#8311 integration). Bulk operations admin: select-all + delete batch, move tra collections, ri-trigger conversions on demand (es. dopo aggiunta nuovo breakpoint), re-tag tutta una collection. Comando artisan `media:regenerate {model?} {collection?}` per ri-generare conversioni (utile dopo cambio dimensioni o nuovo formato output), `media:cleanup-orphans` (rimuove file su disco senza riga DB per fix corruzioni), `media:optimize` (esegue oxipng/mozjpeg/cwebp/avifenc su file esistenti per ridurre size), `media:audit-alt-text` (report immagini senza alt per fix SEO/A11y), `media:export-collection {model} {collection}` (zip download), `media:import-from-folder` (bulk import da directory locale per migrazione legacy). API REST: `POST /api/media` (multipart upload), `GET /api/media/{model_type}/{model_id}/{collection}` (lista ordinata), `PATCH /api/media/{id}` (metadata), `DELETE /api/media/{id}`, `POST /api/media/{id}/reorder`, `POST /api/media/{id}/regenerate-conversions`. Casi d'uso workspace: **Footility** (gallery progetti portfolio sito marketing footility.com — case study clienti con cover + screenshots + diagrammi architettura, modulo «inventory cards» #8316 con hero), **Klabhouse** (gallery corsi/insegnanti con foto profilo + carousel aule + video preview thumbnail, multi-locale alt_text IT/EN), **Holiday Self Drive** (gallery veicoli noleggio: foto multiple per Camper/Auto con cover/interni/esterni/optional collection separate, watermark logo HSD per anti-screenshot, signed URL nessuno — pubbliche), **Gestionale Casarile** (gallery struttura ricettiva: stanze + esterni + spa + ristorante, integrazione con calendario disponibilità #8323), **gestionali B2B Altramusica/LB advisory** (loghi clienti + foto sale + locandine eventi musica, watermark copyright per locandine), **the-body-code/realpilates** (foto trainer + corsi + studio + before/after clienti consenso GDPR signed URL private), **mscarichi** (foto interventi prima/dopo + automezzi + zona servita Google Maps screenshot embed), **prontointervento** (foto squadre + automezzi 24/7 + certificazioni vigili del fuoco), **Footility chat agentic** (avatar utenti + screenshot attachments — collection privata signed URL). Compliance e privacy: GDPR consenso per immagini contenenti persone identificabili (modello Photo+Person con `model_release_signed_at` + scan documento liberatoria, blocco pubblicazione se non firmata), minor consent <16 anni firma genitore obbligatoria, art. 96 LDA Italia diritto immagine, copyright tag `license` + credit obbligatorio per foto da fotografo terzo, EXIF GPS strip automatico opzionale per privacy location (default true per upload pubblico, false admin), retention configurabile per collection (es. screenshot ticket cancellati 90gg post-chiusura), audit log (#8330) upload/delete/view signed-URL. Performance: lazy-load immediato (no JS richiesto via native `loading="lazy"`), IntersectionObserver fallback per LQIP swap, image CDN edge caching, deduplicazione SHA-256 cross-collection opzionale (file binario identico = single storage object + multiple media rows), pre-warming conversioni on upload finish per evitare cold-fetch lento al primo visit. Estensioni roadmap: AI auto-tagging (rilevamento oggetti/scene via #8261 chatbot-vision o OpenAI Vision per generare tags+alt_text+caption automatici, requires consent), AI background-remove per e-commerce prodotti (rembg/remove.bg API), AI super-resolution per immagini low-res (Real-ESRGAN), face detection per blur automatico GDPR su foto pubbliche con persone non liberate, NSFW filter su upload utente (UGC) per moderazione automatica, integrazione Adobe Stock/Unsplash API per pick stock da admin senza upload locale, focal point editor (CropperJS) per smart-crop responsive (es. ritratto verticale → mobile crop volto centrato), galleria pubblica «portfolio» con URL personalizzato `/portfolio/{slug}` SEO indicizzabile (#8311), versioning immagini (rollback a versione precedente dopo crop), batch resize export ZIP, integrazione DAM enterprise (Cloudinary/Bynder/Frontify) opzionale per clienti grandi. Differenze e relazioni: **#8327 CRUD generico anagrafiche datatable Livewire** può usare modulo gallery come componente cella tabella (preview thumbnail + lightbox), **#8311 seo-meta-dinamici-open-graph** consuma collection `og` di gallery per immagine social-share automatica, **#8317 catalogo-prodotti** ogni prodotto ha collection `gallery` + `cover`, **#8313 form-contatti-antispam** allegati form usano collection `attachments` private, **#8326 area-cliente-riservata** documenti caricati cliente sono media con disco S3 visibility private + signed URL, **#8053 cms-lite** editor articoli/pagine usa modulo gallery come block content con drag insert, **#8104 form-contatti-lead** allegati richiesta lead → collection `lead_attachments`, **#8328 export-excel-pdf** può includere immagini in PDF/Excel (Spout/Maatwebsite con embed), **#8261 chatbot-rag** allegati conversazione (utente upload screenshot → AI vision), **#8330 audit-log-azioni-utente** logga eventi upload/delete media. Casi limite/edge: file corrotto in upload → validation error + retry, mime spoofing (`.jpg` con header EXE) → MIME real-check via finfo, immagine 20000x20000px → resize forzato 8000px max + warning user, HEIC/HEIF iPhone → conversione lato server (libheif) o lato client (browser-image-compression), upload concorrente stesso media (race) → lock pessimistic su `order_column`, S3 down → retry exponential backoff + fallback temporaneo disco locale + cron `media:upload-pending` reupload, conversion job OOM su immagine 100MP → resize step intermedio + retry chunked, watermark su PNG trasparente → composite preservando alpha, EXIF orientation rotated → auto-rotate prima conversione (Intervention `orientate()`), filename con caratteri unicode/emoji → sluggify + UUID, browser non supporta AVIF/WebP → fallback automatico via `<picture>`, immagine con CMYK profilo → conversione sRGB forzata. Costo nuovo modulo (greenfield): 0.25g install spatie/laravel-medialibrary + migrazioni base + config disk env, 0.5g trait HasGallery + collection registry per modelli (Pagina/Articolo/Prodotto/etc.) + custom properties schema, 1g componente upload Livewire/Vue drag&drop + preview + progress + retry + validazione, 0.5g cropper modal pre-upload + ratio presets, 0.75g pipeline conversioni queue (thumb/medium/large/webp/avif/og) + responsive breakpoints + LQIP BlurHash, 0.5g lightbox front-end (swipe/keyboard/zoom/deep-link) + componente blade `<x-gallery>` riusabile, 0.5g admin UI gestione media (lista grid/list, edit metadata modal, drag sort Sortable.js, bulk action), 0.5g signed URL private collection + middleware delivery + CDN cache headers, 0.5g watermark + EXIF extract + auto-rotate + sRGB conversion + GPS strip, 0.5g comandi artisan (regenerate/cleanup-orphans/optimize/audit-alt-text/export/import), 0.5g integration Open Graph #8311 + alt_text SEO + sitemap-image XML, 0.5g GDPR liberatoria persona modello + audit log + retention per collection, 0.5g API REST endpoints + Sanctum scopes per area cliente, 0.5g testing E2E (upload happy/error path, conversioni generate, signed URL TTL, ordering, delete cascade). Costo riuso nuovo cliente (modulo pronto): 0.25g configurazione disk env + bucket S3 + CDN URL, 0.25g definizione collections per modelli specifici cliente (cover/gallery/attachments/private), 0.25g brand watermark + colori lightbox, 0.25g testo liberatoria persona cliente-specifico + workflow firma, 0.25g import bulk legacy (foto già caricate altrove → media:import-from-folder), 0.25g test upload happy path + conversion + delivery CDN. Dipendenze: Laravel 10/11, `spatie/laravel-medialibrary` ^11, `intervention/image` ^3 (GD o ImageMagick come backend), opz. AWS SDK PHP per S3, opz. `flysystem-aws-s3-v3` o driver R2/B2/Wasabi compatibili, opz. `kornrunner/blurhash` o `srwiez/thumbhash-php` per LQIP, opz. CDN provider (Cloudflare/BunnyCDN/CloudFront), opz. ImageMagick binary o GD per conversioni, opz. `libheif` per HEIC iPhone, opz. tool ottimizzazione (`oxipng`/`mozjpeg`/`cwebp`/`avifenc`) installati su server o image-optimizer wrapper, opz. integration #8326 per area cliente media, opz. integration #8311 per OG image, opz. integration #8330 audit log, Livewire 3 o Vue 3 o Alpine.js + Sortable.js per UI admin, Cropper.js per crop pre-upload, browser-image-compression JS per client-side resize/HEIC.
Esempi d'uso