PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] ); $families = $meta->query("SELECT id FROM families")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { exit("Cannot connect to meta DB: " . $e->getMessage() . "\n"); } foreach ($families as $family_id) { $db_name = 'househub_f' . $family_id; try { $pdo = new PDO( "mysql:host=$DB_HOST;dbname=$db_name;charset=utf8mb4", $DB_USER, $DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] ); } catch (Exception $e) { continue; } // Skip if todo tables don't exist yet if (!$pdo->query("SHOW TABLES LIKE 'pf_todos'")->fetchColumn()) continue; // Get Discord webhook for this family $ws = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type='todo_settings' AND reference_id='webhook_discord'"); $ws->execute(); $webhook = $ws->fetchColumn(); if (!$webhook) continue; // Tasks due now: due_time reached, not done, not yet notified // Handles: date+time, time only (null date = today), overdue with time $stmt = $pdo->prepare(" SELECT t.id, t.title, t.due_date, t.due_time, l.name AS list_name, l.icon AS list_icon FROM pf_todos t LEFT JOIN pf_todo_lists l ON l.id = t.list_id WHERE t.due_time IS NOT NULL AND (t.due_date IS NULL OR t.due_date <= CURDATE()) AND t.due_time <= CURTIME() AND t.done = 0 AND t.notified = 0 "); $stmt->execute(); foreach ($stmt->fetchAll() as $t) { $time = substr($t['due_time'], 0, 5); $list = $t['list_name'] ? " _({$t['list_icon']} {$t['list_name']})_" : ''; $msg = "⏰ **Rappel** : {$t['title']}{$list} — prévu à {$time}"; $ch = curl_init($webhook); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['username' => 'HouseHub Todo', 'content' => $msg]), ]); curl_exec($ch); $ok = curl_getinfo($ch, CURLINFO_HTTP_CODE) < 300; curl_close($ch); if ($ok) { $pdo->prepare("UPDATE pf_todos SET notified=1 WHERE id=?")->execute([$t['id']]); } } }