getMessage()); return; } // si aucune photo primaire, celle-ci devient primaire $stmt = $db->prepare('SELECT COUNT(*) FROM animal_photos WHERE animal_id=:id AND is_primary=1'); $stmt->execute([':id' => $id]); $hasPrimary = (int) $stmt->fetchColumn() > 0; $stmt = $db->prepare(' INSERT INTO animal_photos(animal_id, filename, original_name, mime, size_bytes, is_primary) VALUES(:aid,:fn,:orig,:mime,:sz,:prim) '); $stmt->execute([ ':aid' => $id, ':fn' => $filename, ':orig' => $orig, ':mime' => $mime, ':sz' => $size, ':prim' => $hasPrimary ? 0 : 1, ]); header('Location: /animal?id=' . $id); exit(); } public static function uploadPhotos(): void { if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { http_response_code(405); return; } $animalId = (int) ($_POST['animal_id'] ?? 0); if ($animalId <= 0) { http_response_code(400); echo h(t('error.bad_request')); return; } if (empty($_FILES['photos'])) { http_response_code(400); echo h(t('error.no_files')); return; } $isPublic = (int) ($_POST['is_public'] ?? 1 ? 1 : 0); $redirectHash = trim((string) ($_POST['redirect_hash'] ?? '')); $db = DB::pdo(); self::ensureAnimalMediaDir($animalId); $files = $_FILES['photos']; $count = is_array($files['name']) ? count($files['name']) : 1; // Y a-t-il déjà une photo principale ? $hasPrimary = (bool) $db ->query( ' SELECT 1 FROM animal_photos WHERE animal_id=' . (int) $animalId . ' AND is_primary=1 LIMIT 1 ', ) ->fetchColumn(); for ($i = 0; $i < $count; $i++) { $name = is_array($files['name']) ? $files['name'][$i] : $files['name']; $tmp = is_array($files['tmp_name']) ? $files['tmp_name'][$i] : $files['tmp_name']; $err = is_array($files['error']) ? $files['error'][$i] : $files['error']; if ($err !== UPLOAD_ERR_OK) { continue; } if (!is_uploaded_file($tmp)) { continue; } if (!self::isAllowedImage($tmp)) { continue; } $base = self::safeFilename(pathinfo($name, PATHINFO_FILENAME)); $targetDir = $isPublic === 1 ? self::animalMediaDir($animalId) : PrivateMediaService::animalDir($animalId); if ($isPublic === 0) { PrivateMediaService::ensure($targetDir); } try { $stored = ImageService::storeUploaded($tmp, $targetDir, $base, 'photo'); $final = $stored['filename']; if ($isPublic === 0) { @chmod($stored['path'], 0600); } } catch (Throwable) { continue; } // RÈGLE : jamais une privée en principale $isPrimary = !$hasPrimary && $isPublic === 1 ? 1 : 0; if ($isPrimary === 1) { $hasPrimary = true; } $st = $db->prepare(' INSERT INTO animal_photos(animal_id, filename, is_primary, is_public) VALUES(:aid,:fn,:p,:pub) '); $st->execute([ ':aid' => $animalId, ':fn' => $final, ':p' => $isPrimary, ':pub' => $isPublic, ]); } $db->prepare( " UPDATE animals SET updated_at=datetime('now') WHERE id=:id ", )->execute([':id' => $animalId]); // Retour direct sur onglet photos $hash = $redirectHash !== '' && $redirectHash[0] === '#' ? $redirectHash : ''; header('Location: /animal?id=' . $animalId . $hash); exit(); } public static function setPrimaryPhoto(): void { if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { http_response_code(405); return; } $animalId = (int) ($_POST['animal_id'] ?? 0); $photoId = (int) ($_POST['photo_id'] ?? 0); if ($animalId <= 0 || $photoId <= 0) { http_response_code(400); return; } $db = DB::pdo(); $stmt = $db->prepare('SELECT filename,is_public FROM animal_photos WHERE id=:pid AND animal_id=:aid'); $stmt->execute([':pid' => $photoId, ':aid' => $animalId]); $photo = $stmt->fetch(PDO::FETCH_ASSOC); if (!$photo) { http_response_code(404); echo h(t('error.photo_not_found')); return; } $moved = false; try { if ((int) $photo['is_public'] !== 1) { PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], true); $moved = true; } $db->beginTransaction(); $db->prepare('UPDATE animal_photos SET is_primary=0 WHERE animal_id=:aid')->execute([':aid' => $animalId]); $db->prepare('UPDATE animal_photos SET is_primary=1,is_public=1 WHERE id=:pid AND animal_id=:aid')->execute( [':pid' => $photoId, ':aid' => $animalId], ); $db->commit(); } catch (Throwable $e) { if ($db->inTransaction()) { $db->rollBack(); } if ($moved) { try { PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], false); } catch (Throwable) { } } throw $e; } // Historique si tu veux if (method_exists(__CLASS__, 'logHistory')) { self::logHistory($animalId, 'photo', 'Photo principale modifiée'); } header('Location: /animal?id=' . $animalId); exit(); } public static function deletePhoto(): void { if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { http_response_code(405); return; } $animalId = (int) ($_POST['animal_id'] ?? 0); $photoId = (int) ($_POST['photo_id'] ?? 0); if ($animalId <= 0 || $photoId <= 0) { http_response_code(400); return; } $db = DB::pdo(); $st = $db->prepare( 'SELECT filename, is_primary, is_public FROM animal_photos WHERE id=:pid AND animal_id=:aid', ); $st->execute([':pid' => $photoId, ':aid' => $animalId]); $row = $st->fetch(PDO::FETCH_ASSOC); if (!$row) { header('Location: /animal?id=' . $animalId); exit(); } $filename = (string) $row['filename']; $wasPrimary = (int) $row['is_primary'] === 1; $db->prepare('DELETE FROM animal_photos WHERE id=:pid AND animal_id=:aid')->execute([ ':pid' => $photoId, ':aid' => $animalId, ]); $path = ((int) ($row['is_public'] ?? 1) === 1 ? self::animalMediaDir($animalId) : PrivateMediaService::animalDir($animalId)) . '/' . $filename; if (is_file($path)) { @unlink($path); } // Si on a supprimé la principale, on met la plus récente restante en principale if ($wasPrimary) { $pid = $db ->query('SELECT id FROM animal_photos WHERE animal_id=' . (int) $animalId . ' ORDER BY id DESC LIMIT 1') ->fetchColumn(); if ($pid) { $db->prepare('UPDATE animal_photos SET is_primary=1 WHERE id=:pid')->execute([':pid' => (int) $pid]); } } $db->prepare("UPDATE animals SET updated_at=datetime('now') WHERE id=:id")->execute([':id' => $animalId]); if (method_exists(__CLASS__, 'logHistory')) { self::logHistory($animalId, 'photo', 'Photo supprimée'); } header('Location: /animal?id=' . $animalId); exit(); } }