Publier Globinours 1.0.0-rc.3

This commit is contained in:
Alexandre NOEL 2026-09-03 12:39:15 +02:00
commit 9a2b4068da
325 changed files with 38230 additions and 20 deletions

View file

@ -0,0 +1,296 @@
<?php
declare(strict_types=1);
trait AnimalPhotoActions
{
public static function uploadPhoto(): void
{
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
http_response_code(405);
echo h(t('error.method_not_allowed'));
return;
}
$id = (int) ($_POST['animal_id'] ?? 0);
if ($id <= 0) {
http_response_code(400);
echo h(t('error.bad_request'));
return;
}
if (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo h(t('error.upload_failed'));
return;
}
$db = DB::pdo();
$tmp = $_FILES['photo']['tmp_name'];
$orig = (string) ($_FILES['photo']['name'] ?? 'photo');
$size = (int) ($_FILES['photo']['size'] ?? 0);
$mime = (string) ($_FILES['photo']['type'] ?? '');
$base = preg_replace('/[^a-zA-Z0-9._-]+/', '_', pathinfo($orig, PATHINFO_FILENAME));
$base = $base ?: 'photo';
$dir = __DIR__ . '/../../public/media/animals/' . $id;
try {
$stored = ImageService::storeUploaded($tmp, $dir, $base, 'photo');
$filename = $stored['filename'];
$mime = $stored['mime'];
$size = $stored['size'];
} catch (Throwable $e) {
http_response_code(400);
echo h($e->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();
}
}