(int) $r['id'], 'folder_id' => $r['folder_id'] !== null ? (int) $r['folder_id'] : null, 'kind' => $r['kind'], 'title' => $r['title'], 'description' => $r['description'] ?? null, 'url' => item_url($r), 'size' => $r['size'] !== null ? (int) $r['size'] : null, 'mime' => $r['mime'], 'status' => $r['status'], 'submitted_at' => $r['submitted_at'] ?? null, ]; } try { $method = $_SERVER['REQUEST_METHOD']; $action = $_GET['action'] ?? ''; // ---- File d'attente de modération (admin) ----------------------------- if ($method === 'GET' && $action === 'queue') { $user = require_login(); if ($user['role'] !== 'super' && $user['role'] !== 'perimeter') { json_out([]); // commentateur : aucune file de modération } $sql = "SELECT i.*, e.nom AS etab_nom, e.com AS etab_com, e.depc AS etab_depc, e.dep AS etab_dep FROM items i JOIN etablissements e ON e.id = i.uai"; $args = []; if ($user['role'] === 'super') { $sql .= " WHERE i.status = 'pending'"; } else { // Périmètre personnel (legacy) OU groupe « valideur » de l'utilisateur. $sql .= " WHERE i.status = 'pending' AND ( EXISTS (SELECT 1 FROM user_scopes s WHERE s.user_id = ? AND s.com = e.com AND s.depc = e.depc) OR EXISTS (SELECT 1 FROM group_members gm JOIN user_groups g ON g.id = gm.group_id AND g.can_validate = 1 JOIN group_scopes gs ON gs.group_id = g.id AND gs.com = e.com AND gs.depc = e.depc WHERE gm.user_id = ?))"; $args[] = $user['id']; $args[] = $user['id']; } $sql .= ' ORDER BY i.submitted_at ASC'; $st = db()->prepare($sql); $st->execute($args); $out = []; foreach ($st as $r) { $out[] = shape_item($r) + [ 'uai' => $r['uai'], 'etab' => $r['etab_nom'] . ' — ' . $r['etab_com'] . ' (' . $r['etab_depc'] . ')', 'submitter_name' => $r['submitter_name'], 'submitter_email' => $r['submitter_email'], ]; } json_out($out); } // ---- Liste publique (éléments validés) -------------------------------- if ($method === 'GET') { $uai = trim((string) ($_GET['uai'] ?? '')); if ($uai === '') { json_error('Paramètre uai requis.'); } $sql = "SELECT * FROM items WHERE uai = ? AND status = 'approved'"; $args = [$uai]; if (!empty($_GET['kind'])) { $sql .= ' AND kind = ?'; $args[] = $_GET['kind']; } $sql .= ' ORDER BY submitted_at DESC, id DESC'; $st = db()->prepare($sql); $st->execute($args); $out = []; foreach ($st as $r) { $out[] = shape_item($r); } json_out($out); } // ---- Validation / rejet (admin) --------------------------------------- if ($method === 'POST' && $action === 'validate') { $user = require_login(); $b = body_json(); $id = (int) ($b['id'] ?? 0); $decision = $b['decision'] ?? ''; if ($id <= 0 || !in_array($decision, ['approve', 'reject'], true)) { json_error('Paramètres invalides (id, decision).'); } $st = db()->prepare('SELECT uai FROM items WHERE id = ?'); $st->execute([$id]); $uai = $st->fetchColumn(); if ($uai === false) { json_error('Élément introuvable.', 404); } if (!can_validate_uai($user, (string) $uai)) { json_error('Hors de votre périmètre.', 403); } $status = $decision === 'approve' ? 'approved' : 'rejected'; $reason = $decision === 'reject' ? (trim((string) ($b['reason'] ?? '')) ?: null) : null; db()->prepare( 'UPDATE items SET status = ?, reject_reason = ?, validated_by = ?, validated_at = NOW() WHERE id = ?' )->execute([$status, $reason, $user['id'], $id]); json_out(['ok' => true, 'id' => $id, 'status' => $status]); } // ---- Édition du libellé / description (valideurs) --------------------- if ($method === 'POST' && $action === 'update') { $user = require_login(); $b = body_json(); $id = (int) ($b['id'] ?? 0); if ($id <= 0) { json_error('id requis.'); } $st = db()->prepare('SELECT uai FROM items WHERE id = ?'); $st->execute([$id]); $uai = $st->fetchColumn(); if ($uai === false) { json_error('Élément introuvable.', 404); } if (!can_validate_uai($user, (string) $uai)) { json_error('Hors de votre périmètre.', 403); } $title = trim((string) ($b['title'] ?? '')); $description = trim((string) ($b['description'] ?? '')); if ($title === '') { json_error('Le libellé ne peut pas être vide.'); } db()->prepare('UPDATE items SET title = ?, description = ? WHERE id = ?') ->execute([$title, $description !== '' ? $description : null, $id]); json_out(['ok' => true, 'id' => $id]); } // ---- Soumission (tout internaute) ------------------------------------- if ($method === 'POST') { $uai = trim((string) ($_GET['uai'] ?? $_POST['uai'] ?? '')); if ($uai === '') { json_error('Paramètre uai requis.'); } $chk = db()->prepare('SELECT 1 FROM etablissements WHERE id = ?'); $chk->execute([$uai]); if (!$chk->fetchColumn()) { json_error('Établissement inconnu.', 404); } $kind = $_POST['kind'] ?? ''; if (!in_array($kind, ['file', 'photo', 'link'], true)) { json_error('Type invalide (file, photo ou link).'); } $folderId = (isset($_POST['folder_id']) && $_POST['folder_id'] !== '') ? (int) $_POST['folder_id'] : null; if ($folderId !== null) { $f = db()->prepare('SELECT 1 FROM folders WHERE id = ?'); $f->execute([$folderId]); if (!$f->fetchColumn()) { json_error('Dossier inconnu.'); } } $title = trim((string) ($_POST['title'] ?? '')); $description = trim((string) ($_POST['description'] ?? '')) ?: null; $submitterName = trim((string) ($_POST['submitter_name'] ?? '')) ?: null; $submitterMail = trim((string) ($_POST['submitter_email'] ?? '')) ?: null; $filename = null; $url = null; $size = null; $mime = null; if ($kind === 'link') { $url = trim((string) ($_POST['url'] ?? '')); if (!preg_match('#^https?://#i', $url)) { json_error('URL invalide (doit commencer par http:// ou https://).'); } if ($title === '') { $title = $url; } } else { if (empty($_FILES['file']) || (int) $_FILES['file']['error'] !== UPLOAD_ERR_OK) { json_error('Fichier requis.'); } $tmp = $_FILES['file']['tmp_name']; $orig = (string) $_FILES['file']['name']; $size = (int) $_FILES['file']['size']; $mime = mime_content_type($tmp) ?: ($_FILES['file']['type'] ?? 'application/octet-stream'); if ($kind === 'photo' && strncmp($mime, 'image/', 6) !== 0) { json_error('Le fichier n’est pas une image.'); } $dir = dirname(__DIR__) . '/uploads/' . kind_dir($kind); if (!is_dir($dir)) { @mkdir($dir, 0777, true); } $ext = strtolower(preg_replace('/[^A-Za-z0-9]/', '', pathinfo($orig, PATHINFO_EXTENSION) ?: '')); $filename = bin2hex(random_bytes(8)) . ($ext !== '' ? '.' . $ext : ''); if (!move_uploaded_file($tmp, $dir . '/' . $filename)) { json_error('Échec de l’enregistrement du fichier.', 500); } if ($title === '') { $title = $orig; } } db()->prepare( 'INSERT INTO items (uai, folder_id, kind, title, description, filename, url, size, mime, status, submitter_name, submitter_email) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, \'pending\', ?, ?)' )->execute([$uai, $folderId, $kind, $title, $description, $filename, $url, $size, $mime, $submitterName, $submitterMail]); json_out(['ok' => true, 'id' => (int) db()->lastInsertId(), 'status' => 'pending'], 201); } // ---- Suppression (admin) ---------------------------------------------- if ($method === 'DELETE') { $user = require_login(); $id = (int) ($_GET['id'] ?? 0); if ($id <= 0) { json_error('id requis.'); } $st = db()->prepare('SELECT uai, kind, filename FROM items WHERE id = ?'); $st->execute([$id]); $row = $st->fetch(); if (!$row) { json_error('Élément introuvable.', 404); } if (!can_validate_uai($user, (string) $row['uai'])) { json_error('Hors de votre périmètre.', 403); } db()->prepare('DELETE FROM comments WHERE item_id = ?')->execute([$id]); db()->prepare('DELETE FROM items WHERE id = ?')->execute([$id]); if ($row['filename']) { @unlink(dirname(__DIR__) . '/uploads/' . kind_dir($row['kind']) . '/' . $row['filename']); } json_out(['deleted' => $id]); } json_error('Méthode non supportée.', 405); } catch (Throwable $e) { json_error('Erreur : ' . $e->getMessage(), 500); }