false, 'error' => 'Forbidden'], 403); } $report = ['published' => 0, 'reminders' => 0, 'offers_expired' => 0, 'promoted' => 0]; // Default scuola $opt = []; try { $opt = $pdo->query("SELECT * FROM `option` LIMIT 1")->fetch(PDO::FETCH_ASSOC) ?: []; } catch (Throwable $e) { $opt = []; } $defRemEnabled = ($opt['event_reminder_enabled'] ?? 'N') === 'Y'; $defRemHours = (int) ($opt['event_reminder_hours'] ?? 24); $defOfferHours = (int) ($opt['event_offer_expiry_hours'] ?? 24); /* 1) Pubblica gli eventi programmati arrivati a scadenza */ try { $pub = $pdo->prepare("UPDATE events SET status = 'published', updated_at = NOW() WHERE status = 'scheduled' AND publish_at IS NOT NULL AND publish_at <= NOW()"); $pub->execute(); $report['published'] = $pub->rowCount(); } catch (Throwable $e) { error_log('[cron publish] ' . $e->getMessage()); } /* 2) Promemoria automatici (una sola volta per evento) */ try { $evs = $pdo->query("SELECT * FROM events WHERE status = 'published' AND reminder_sent_at IS NULL AND starts_at > NOW()") ->fetchAll(PDO::FETCH_ASSOC); foreach ($evs as $ev) { $enabled = $ev['reminder_enabled'] !== null ? ($ev['reminder_enabled'] === 'Y') : $defRemEnabled; if (!$enabled) { continue; } $hours = $ev['reminder_hours'] !== null ? (int) $ev['reminder_hours'] : $defRemHours; if (strtotime($ev['starts_at']) - $hours * 3600 > time()) { continue; // non ancora l'ora del promemoria } $ps = $pdo->prepare("SELECT * FROM event_bookings WHERE event_id = ? AND status = 'confirmed'"); $ps->execute([$ev['id']]); foreach ($ps->fetchAll(PDO::FETCH_ASSOC) as $p) { try { yse_mail_reminder($p, $ev); $report['reminders']++; } catch (Throwable $me) { error_log('[cron reminder] ' . $me->getMessage()); } } $pdo->prepare("UPDATE events SET reminder_sent_at = NOW() WHERE id = ?")->execute([$ev['id']]); } } catch (Throwable $e) { error_log('[cron reminders] ' . $e->getMessage()); } /* 3) Scadenza offerte lista d'attesa */ try { $offers = $pdo->query("SELECT b.*, e.offer_expiry_hours AS ev_offer_hours FROM event_bookings b JOIN events e ON e.id = b.event_id WHERE b.status = 'offered' AND b.offered_at IS NOT NULL") ->fetchAll(PDO::FETCH_ASSOC); foreach ($offers as $o) { $hours = $o['ev_offer_hours'] !== null ? (int) $o['ev_offer_hours'] : $defOfferHours; if (strtotime($o['offered_at']) + $hours * 3600 > time()) { continue; // offerta ancora valida } $promoted = []; try { $pdo->beginTransaction(); $pdo->prepare("SELECT id FROM events WHERE id = ? FOR UPDATE")->execute([(int) $o['event_id']]); $st = $pdo->prepare("SELECT status FROM event_bookings WHERE id = ?"); $st->execute([(int) $o['id']]); if ($st->fetchColumn() === 'offered') { $pdo->prepare("UPDATE event_bookings SET status = 'cancelled', updated_at = NOW() WHERE id = ?") ->execute([(int) $o['id']]); $promoted = promoteFromWaitlist($pdo, (int) $o['event_id']); $report['offers_expired']++; } $pdo->commit(); if (!empty($promoted)) { $ev = $pdo->prepare("SELECT * FROM events WHERE id = ?"); $ev->execute([(int) $o['event_id']]); $evRow = $ev->fetch(PDO::FETCH_ASSOC); foreach ($promoted as $pr) { try { yse_mail_promotion($pr, $evRow); $report['promoted']++; } catch (Throwable $pe) { error_log('[cron promo] ' . $pe->getMessage()); } } } } catch (Throwable $te) { if ($pdo->inTransaction()) { $pdo->rollBack(); } error_log('[cron offer] ' . $te->getMessage()); } } } catch (Throwable $e) { error_log('[cron offers] ' . $e->getMessage()); } send_json(['ok' => true, 'report' => $report, 'at' => date('c')]);