un solo evento * ?ids=12,15,20 -> solo questi eventi (nell'ordine per data) * ?limit=3 -> massimo N eventi * Senza parametri: tutti i pubblicati futuri, per data crescente. * * Regole di visibilità: * status = 'published' AND COALESCE(ends_at, starts_at) >= NOW() * (l'evento sparisce da solo quando è terminato) */ try { $pdo = pdo_conn(); $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; $idsParam = trim($_GET['ids'] ?? ''); $limit = isset($_GET['limit']) ? max(1, min(50, (int) $_GET['limit'])) : 0; $where = ["e.status = 'published'", "COALESCE(e.ends_at, e.starts_at) >= NOW()"]; $params = []; if ($id > 0) { $where[] = 'e.id = ?'; $params[] = $id; } elseif ($idsParam !== '') { $ids = array_values(array_filter(array_map('intval', explode(',', $idsParam)), function ($v) { return $v > 0; })); if (!empty($ids)) { $where[] = 'e.id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')'; foreach ($ids as $v) { $params[] = $v; } } else { send_json(['ok' => true, 'count' => 0, 'events' => []]); } } $sql = "SELECT e.*, u.first_name AS cf, u.last_name AS cl, COALESCE(b.occupied, 0) AS occupied, COALESCE(w.waiting, 0) AS waiting FROM events e LEFT JOIN auth_users u ON u.id = e.user_id LEFT JOIN ( SELECT event_id, SUM(seats) AS occupied FROM event_bookings WHERE status IN ('confirmed','pending','offered') GROUP BY event_id ) b ON b.event_id = e.id LEFT JOIN ( SELECT event_id, SUM(seats) AS waiting FROM event_bookings WHERE status = 'waiting' GROUP BY event_id ) w ON w.event_id = e.id WHERE " . implode(' AND ', $where) . " ORDER BY e.starts_at ASC"; if ($limit > 0) { $sql .= ' LIMIT ' . (int) $limit; } $stmt = $pdo->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $symbols = ['EUR' => '€', 'USD' => '$', 'GBP' => '£', 'CHF' => 'CHF']; $events = []; foreach ($rows as $r) { $maxSeats = (int) $r['max_seats']; $occupied = (int) $r['occupied']; $remaining = max(0, $maxSeats - $occupied); $gallery = []; if (!empty($r['gallery'])) { $g = json_decode($r['gallery'], true); if (is_array($g)) { foreach ($g as $path) { if (is_string($path) && $path !== '') { $gallery[] = public_url($path); } } } } $organizer = trim((string) $r['organizer_name']) !== '' ? $r['organizer_name'] : trim(($r['cf'] ?? '') . ' ' . ($r['cl'] ?? '')); $events[] = [ 'id' => (int) $r['id'], 'title' => $r['title'], 'slug' => $r['slug'], 'description' => $r['description'], // HTML (sanificare al rendering in WP) 'instructions' => $r['instructions'], 'what_to_bring' => $r['what_to_bring'], 'suitable_for' => $r['suitable_for'], 'organizer' => $organizer !== '' ? $organizer : null, 'starts_at' => $r['starts_at'], 'ends_at' => $r['ends_at'], 'address' => $r['address'], 'lat' => $r['lat'] !== null ? (float) $r['lat'] : null, 'lng' => $r['lng'] !== null ? (float) $r['lng'] : null, 'cost' => (float) $r['cost'], 'currency' => $r['currency'], 'currency_symbol' => $symbols[$r['currency']] ?? $r['currency'], 'color' => $r['color'], 'cover_image' => public_url($r['cover_image']), 'gallery' => $gallery, 'max_seats' => $maxSeats, 'max_per_booking' => (int) $r['max_per_booking'], 'occupied' => $occupied, 'remaining' => $remaining, 'sold_out' => $remaining <= 0, 'waitlist_enabled' => ($r['waitlist_enabled'] ?? 'N') === 'Y', 'waiting' => (int) $r['waiting'], ]; } send_json(['ok' => true, 'count' => count($events), 'events' => $events]); } catch (Throwable $ex) { error_log('[api/events] ' . $ex->getMessage()); send_json(['ok' => false, 'error' => 'Errore nel recupero degli eventi.'], 500); }