diff --git a/public/newsletter_api.php b/public/newsletter_api.php index 4876947..f893afa 100644 --- a/public/newsletter_api.php +++ b/public/newsletter_api.php @@ -167,9 +167,15 @@ try { SET title = ?, subject = ?, preheader = ?, blocks = ?, settings = ?, revision = revision + 1, updated_by = ?, updated_at = NOW() WHERE iddraft = ? AND deleted_at IS NULL" . ($force ? '' : ' AND revision = ?'); - $params = [$c['title'], $c['subject'], $c['preheader'], - json_encode($c['blocks'], JSON_UNESCAPED_UNICODE), json_encode($c['settings'], JSON_UNESCAPED_UNICODE), - $userId, $id]; + $params = [ + $c['title'], + $c['subject'], + $c['preheader'], + json_encode($c['blocks'], JSON_UNESCAPED_UNICODE), + json_encode($c['settings'], JSON_UNESCAPED_UNICODE), + $userId, + $id + ]; if (!$force) $params[] = $rev; $st = $pdo->prepare($sql); @@ -178,7 +184,9 @@ try { $d = draftRow($pdo, $id); if ($st->rowCount() === 0) { fail('Questa newsletter è stata salvata da un\'altra parte (un\'altra scheda o un altro computer).', 409, [ - 'conflict' => true, 'revision' => (int) $d['revision'], 'updated_at' => $d['updated_at'], + 'conflict' => true, + 'revision' => (int) $d['revision'], + 'updated_at' => $d['updated_at'], ]); } @@ -251,67 +259,93 @@ try { case 'upload_image': if (!$isPost) fail('Metodo non consentito.', 405); $f = $_FILES['image'] ?? null; - if (!$f || $f['error'] !== UPLOAD_ERR_OK) fail('Caricamento non riuscito.'); + if (!$f || ($f['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { + $why = [ + UPLOAD_ERR_INI_SIZE => 'il file supera il limite del server (upload_max_filesize)', + UPLOAD_ERR_FORM_SIZE => 'il file è troppo grande', + UPLOAD_ERR_PARTIAL => 'caricamento interrotto', + UPLOAD_ERR_NO_FILE => 'nessun file ricevuto', + UPLOAD_ERR_NO_TMP_DIR => 'manca la cartella temporanea sul server', + UPLOAD_ERR_CANT_WRITE => 'il server non riesce a scrivere il file', + ][$f['error'] ?? UPLOAD_ERR_NO_FILE] ?? 'errore sconosciuto'; + fail('Caricamento non riuscito: ' . $why . '.'); + } if ($f['size'] > 15 * 1024 * 1024) fail('Immagine troppo grande (massimo 15 MB).'); $info = @getimagesize($f['tmp_name']); $mime = $info['mime'] ?? ''; - if (!in_array($mime, ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], true)) { - fail('Formato non supportato: usa JPG, PNG, GIF o WebP.'); - } + $ext = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp'][$mime] ?? null; + if (!$ext) fail('Formato non supportato: usa JPG, PNG, GIF o WebP.'); $dirRel = 'assets/uploads/newsletter/' . date('Y/m'); $dirAbs = __DIR__ . '/' . $dirRel; - if (!is_dir($dirAbs) && !mkdir($dirAbs, 0755, true)) fail('Impossibile creare la cartella delle immagini.', 500); + if (!is_dir($dirAbs) && !@mkdir($dirAbs, 0755, true)) { + fail('Impossibile creare la cartella ' . $dirRel . ': controlla i permessi di assets/uploads.', 500); + } + if (!is_writable($dirAbs)) fail('La cartella ' . $dirRel . ' non è scrivibile: controlla i permessi.', 500); $name = date('Ymd_His') . '_' . bin2hex(random_bytes(4)); [$w, $h] = $info; + $note = ''; - if ($mime === 'image/gif') { - // Le GIF possono essere animate: le teniamo come sono - $file = $name . '.gif'; - move_uploaded_file($f['tmp_name'], $dirAbs . '/' . $file); + // Si può ottimizzare? Serve GD con il formato giusto + $reader = ['image/jpeg' => 'imagecreatefromjpeg', 'image/png' => 'imagecreatefrompng', 'image/webp' => 'imagecreatefromwebp'][$mime] ?? null; + $canResize = $mime !== 'image/gif' && $reader && function_exists($reader) && function_exists('imagecreatetruecolor'); + + if (!$canResize) { + // GIF (forse animata) oppure server senza GD: salviamo l'originale + if ($mime !== 'image/gif' && $f['size'] > 3 * 1024 * 1024) { + fail('Il server non può ridimensionare le immagini (manca la libreria GD) e questa supera 3 MB: riducila prima di caricarla.'); + } + $file = $name . '.' . $ext; + if (!move_uploaded_file($f['tmp_name'], $dirAbs . '/' . $file)) fail('Impossibile salvare l\'immagine.', 500); + if ($mime !== 'image/gif') $note = 'Caricata senza ottimizzazione (sul server manca la libreria GD).'; } else { - $src = match ($mime) { - 'image/jpeg' => imagecreatefromjpeg($f['tmp_name']), - 'image/png' => imagecreatefrompng($f['tmp_name']), - 'image/webp' => imagecreatefromwebp($f['tmp_name']), - }; - if (!$src) fail('Immagine non leggibile.'); + try { + @ini_set('memory_limit', '512M'); // le foto dal telefono sono grandi + $src = @$reader($f['tmp_name']); + if (!$src) fail('Immagine non leggibile o danneggiata.'); - // Foto dal telefono: raddrizza secondo l'orientamento EXIF - if ($mime === 'image/jpeg' && function_exists('exif_read_data')) { - $o = (int) (@exif_read_data($f['tmp_name'])['Orientation'] ?? 1); - $rot = [3 => 180, 6 => -90, 8 => 90][$o] ?? 0; - if ($rot) { $src = imagerotate($src, $rot, 0); $w = imagesx($src); $h = imagesy($src); } + // Foto dal telefono: raddrizza secondo l'orientamento EXIF + if ($mime === 'image/jpeg' && function_exists('exif_read_data')) { + $o = (int) (@exif_read_data($f['tmp_name'])['Orientation'] ?? 1); + $rot = [3 => 180, 6 => -90, 8 => 90][$o] ?? 0; + if ($rot) { + $src = imagerotate($src, $rot, 0); + $w = imagesx($src); + $h = imagesy($src); + } + } + + $nw = min($w, 1200); // il doppio della larghezza email: nitida sugli schermi retina + $nh = max(1, (int) round($h * $nw / $w)); + + // PNG con trasparenza resta PNG, il resto diventa JPG (molto più leggero) + $keepPng = false; + if ($mime === 'image/png') { + $ct = ord((string) file_get_contents($f['tmp_name'], false, null, 25, 1)); + $keepPng = in_array($ct, [4, 6], true); + } + + $dst = imagecreatetruecolor($nw, $nh); + if ($keepPng) { + imagealphablending($dst, false); + imagesavealpha($dst, true); + } else { + imagefill($dst, 0, 0, imagecolorallocate($dst, 255, 255, 255)); + } + imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h); + + $file = $name . ($keepPng ? '.png' : '.jpg'); + $ok = $keepPng ? imagepng($dst, $dirAbs . '/' . $file, 9) : imagejpeg($dst, $dirAbs . '/' . $file, 82); + imagedestroy($src); + imagedestroy($dst); + if (!$ok) fail('Impossibile salvare l\'immagine ottimizzata.', 500); + [$w, $h] = [$nw, $nh]; + } catch (Throwable $e) { + error_log('newsletter_api [upload_image]: ' . $e->getMessage()); + fail('Ottimizzazione non riuscita: ' . $e->getMessage(), 500); } - - $maxW = 1200; // il doppio della larghezza email: nitida sugli schermi retina - $nw = min($w, $maxW); - $nh = (int) round($h * $nw / $w); - - // PNG con trasparenza resta PNG, tutto il resto diventa JPG (molto più leggero) - $keepPng = false; - if ($mime === 'image/png') { - $ct = ord(file_get_contents($f['tmp_name'], false, null, 25, 1)); - $keepPng = in_array($ct, [4, 6], true); - } - - $dst = imagecreatetruecolor($nw, $nh); - if ($keepPng) { - imagealphablending($dst, false); - imagesavealpha($dst, true); - } else { - imagefill($dst, 0, 0, imagecolorallocate($dst, 255, 255, 255)); - } - imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h); - - $file = $name . ($keepPng ? '.png' : '.jpg'); - $ok = $keepPng ? imagepng($dst, $dirAbs . '/' . $file, 9) : imagejpeg($dst, $dirAbs . '/' . $file, 82); - imagedestroy($src); - imagedestroy($dst); - if (!$ok) fail('Impossibile salvare l\'immagine.', 500); - [$w, $h] = [$nw, $nh]; } out([ @@ -320,6 +354,7 @@ try { 'width' => $w, 'height' => $h, 'kb' => (int) round(filesize($dirAbs . '/' . $file) / 1024), + 'note' => $note, ]); case 'send_test': @@ -413,8 +448,16 @@ try { $imp = NewsletterWp::import($wpId); // Se in WordPress non è ancora partita, la colleghiamo: "Pubblica" aggiornerà quella $status = NewsletterWp::statuses([$wpId])[$wpId] ?? ''; - $id = createDraft($pdo, $userId, $imp['subject'] ?: 'Importata da WordPress', $imp['subject'], $imp['preheader'], - $imp['blocks'], $imp['settings'], $status === 'new' ? $wpId : null); + $id = createDraft( + $pdo, + $userId, + $imp['subject'] ?: 'Importata da WordPress', + $imp['subject'], + $imp['preheader'], + $imp['blocks'], + $imp['settings'], + $status === 'new' ? $wpId : null + ); out(['success' => true, 'id' => $id, 'linked' => $status === 'new']); default: diff --git a/public/newsletter_editor.php b/public/newsletter_editor.php index fad3c2f..1bc1ac9 100644 --- a/public/newsletter_editor.php +++ b/public/newsletter_editor.php @@ -1219,7 +1219,8 @@ $jsonFlags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_ $('#nlChecks').innerHTML = lastChecks.map(c => '