$v) echo " - $k: $v" . PHP_EOL; } } else { echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } } try { $db = Database::getInstance(); } catch (Exception $e) { out(['success' => false, 'message' => 'DB-Fehler: ' . $e->getMessage()], $isCli); exit; } // Token prüfen (gleicher Token wie cron_sync) $cronToken = $db->getSetting('cron_token', ''); $provided = $isCli ? ($argv[1] ?? '') : ($_GET['token'] ?? ''); if (empty($cronToken)) { out(['success' => false, 'message' => 'Kein Cron-Token konfiguriert.'], $isCli); exit; } if (!hash_equals($cronToken, (string)$provided)) { out(['success' => false, 'message' => 'Ungültiger Token'], $isCli); exit; } $expiredDays = (int)$db->getSetting('cleanup_expired_days', 0); $auditDays = (int)$db->getSetting('cleanup_audit_days', 0); $loginDays = (int)$db->getSetting('cleanup_login_days', 30); $deleted = []; try { // Abgelaufene Voucher aus der DB entfernen (nur lokale Historie) if ($expiredDays > 0) { $stmt = $db->query( "DELETE FROM vouchers WHERE status = 'expired' AND expires_at IS NOT NULL AND expires_at < DATE_SUB(NOW(), INTERVAL ? DAY)", [$expiredDays] ); $deleted['vouchers_expired'] = $stmt->rowCount(); } // Audit-Log nach Aufbewahrungsfrist löschen if ($auditDays > 0) { try { $stmt = $db->query( "DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)", [$auditDays] ); $deleted['audit_log'] = $stmt->rowCount(); } catch (Exception $e) { /* Tabelle evtl. nicht vorhanden */ } } // Alte Login-Versuche entfernen if ($loginDays > 0) { try { $stmt = $db->query( "DELETE FROM login_attempts WHERE attempted_at < DATE_SUB(NOW(), INTERVAL ? DAY)", [$loginDays] ); $deleted['login_attempts'] = $stmt->rowCount(); } catch (Exception $e) { /* ignore */ } } // Abgelaufene / benutzte Passwort-Reset-Tokens immer entfernen try { $stmt = $db->query( "DELETE FROM password_reset_tokens WHERE used = 1 OR expires_at < NOW()" ); $deleted['reset_tokens'] = $stmt->rowCount(); } catch (Exception $e) { /* Tabelle evtl. nicht vorhanden */ } // Optionaler täglicher Update-Check mit Webhook-Hinweis (Updater isoliert, // daher nur falls vorhanden und nur einmal pro Tag). $bootstrap = __DIR__ . '/updater/bootstrap.php'; if (is_file($bootstrap)) { $lastCheck = $db->getSetting('last_update_check', ''); if (!$lastCheck || strtotime($lastCheck) < strtotime('-23 hours')) { try { require_once $bootstrap; $mgr = \Updater\UpdaterFactory::create($db, null); $res = $mgr->checkForUpdates(); if (!empty($res['has_update'])) { Notifier::updateAvailable($res['latest_sha'] ?? ''); } } catch (\Throwable $e) { /* Update-Check nie blockierend */ } $db->setSetting('last_update_check', date('Y-m-d H:i:s')); } } $db->query( "INSERT INTO settings (setting_key, setting_value) VALUES ('last_cleanup', NOW()) ON DUPLICATE KEY UPDATE setting_value = NOW()" ); out([ 'success' => true, 'message' => 'Cleanup abgeschlossen', 'deleted' => $deleted, 'timestamp' => date('Y-m-d H:i:s'), ], $isCli); } catch (Exception $e) { out(['success' => false, 'message' => 'Fehler: ' . $e->getMessage(), 'deleted' => $deleted], $isCli); }