0, 'log' => []]; set_setting_local($pdo, 'cron_anonymize_data_last_run', date('Y-m-d H:i:s')); // Einstellungen aus DB laden $enabled = get_setting_local($pdo, 'retention_enabled', '0'); if ($enabled !== '1') { $result['log'][] = '[skip] Automatische Anonymisierung ist deaktiviert.'; return $result; } $months = max(1, (int) get_setting_local($pdo, 'retention_months', '24')); $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); // Cutoff-Datum berechnen $cutoff = date('Y-m-d H:i:s', strtotime("-{$months} months")); if ($verbose) { $result['log'][] = "[info] Anonymisiere Tickets geschlossen/gelöst vor: $cutoff"; } // Betroffene Tickets finden (noch nicht anonymisiert = email enthält kein 'anon_') $stmt = $pdo->prepare( "SELECT id, requester_email, user_id FROM tickets WHERE status IN ('closed', 'resolved') AND updated_at < ? AND requester_email NOT LIKE 'anon\\_%' ESCAPE '\\\\' LIMIT 500" ); $stmt->execute([$cutoff]); $tickets = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($tickets)) { $result['log'][] = '[info] Keine Tickets zur Anonymisierung gefunden.'; return $result; } $updTicket = $pdo->prepare( "UPDATE tickets SET requester_name = 'Anonymisiert', requester_email = ?, requester_ip = '0.0.0.0' WHERE id = ?" ); // User anonymisieren wenn er keine anderen offenen Tickets mehr hat $checkUser = $pdo->prepare( "SELECT COUNT(*) FROM tickets WHERE user_id = ? AND status NOT IN ('closed', 'resolved') AND id != ?" ); $updUser = $pdo->prepare( "UPDATE users SET name = 'Anonymisiert', email = ?, phone = NULL WHERE id = ? AND email NOT LIKE 'anon\\_%' ESCAPE '\\\\'" ); foreach ($tickets as $ticket) { $anonEmail = 'anon_' . substr(hash('sha256', $ticket['requester_email'] . 'deskhub-anon'), 0, 16) . '@deleted.local'; try { $pdo->beginTransaction(); $updTicket->execute([$anonEmail, $ticket['id']]); // User-Account anonymisieren wenn keine anderen offenen Tickets if ($ticket['user_id']) { $checkUser->execute([$ticket['user_id'], $ticket['id']]); if ((int) $checkUser->fetchColumn() === 0) { $updUser->execute([$anonEmail, $ticket['user_id']]); } } $pdo->commit(); $result['count']++; if ($verbose) { $result['log'][] = "[ok] Ticket #{$ticket['id']} anonymisiert"; } } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); $result['log'][] = "[err] Ticket #{$ticket['id']}: " . $e->getMessage(); } } return $result; } /** * Liest eine Einstellung direkt aus der DB (ohne Bootstrap-Abhängigkeit). */ function get_setting_local(PDO $pdo, string $key, string $default = ''): string { try { $stmt = $pdo->prepare('SELECT setting_value FROM settings WHERE setting_key = ?'); $stmt->execute([$key]); $val = $stmt->fetchColumn(); return $val !== false ? (string) $val : $default; } catch (Throwable $e) { return $default; } } /** * Speichert eine Einstellung direkt in der DB (ohne Bootstrap-Abhängigkeit, treiber-unabhängiges Upsert). */ function set_setting_local(PDO $pdo, string $key, string $value): void { try { $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); if ($driver === 'sqlite') { $pdo->prepare('INSERT OR REPLACE INTO settings (setting_key, setting_value) VALUES (?,?)') ->execute([$key, $value]); } else { $pdo->prepare( 'INSERT INTO settings (setting_key, setting_value) VALUES (?,?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)' )->execute([$key, $value]); } } catch (Throwable $e) { // Best effort – Anonymisierung soll nicht daran scheitern. } }