Upload via GUI (258 Dateien)
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* cron/anonymize_data.php
|
||||
*
|
||||
* Anonymisiert personenbezogene Daten aus geschlossenen/gelösten Tickets
|
||||
* nach der in den Einstellungen konfigurierten Aufbewahrungsdauer.
|
||||
*
|
||||
* Crontab: 0 3 * * * php /pfad/zu/deskhub/cron/anonymize_data.php
|
||||
*
|
||||
* Was wird anonymisiert:
|
||||
* - tickets.requester_name → "Anonymisiert"
|
||||
* - tickets.requester_email → "anon_[hash]@deleted.local"
|
||||
* - tickets.requester_ip → "0.0.0.0"
|
||||
* - users.name + email → wenn der User keine anderen offenen Tickets hat
|
||||
*
|
||||
* Was bleibt erhalten:
|
||||
* - Ticket-Nummer, Betreff, Status, Priorität, Abteilung
|
||||
* - Alle Nachrichten (ticket_threads) inkl. Zeitstempel
|
||||
* - Statistiken / Auswertungen
|
||||
*/
|
||||
|
||||
// Direkter Web-Zugriff (Browser/URL) wird geblockt. Ausnahmen: echter CLI-Cronjob
|
||||
// sowie Einbindung per require_once aus dem authentifizierten Backend (staff/settings.php),
|
||||
// das vor dem require_once die Konstante DESKHUB_CRON_RUNNER definiert.
|
||||
if (php_sapi_name() !== 'cli' && !defined('DESKHUB_CRON_RUNNER')) {
|
||||
http_response_code(403);
|
||||
exit('Kein Web-Zugriff.');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$pdo = get_db();
|
||||
$result = run_anonymization($pdo, true);
|
||||
|
||||
foreach ($result['log'] as $line) {
|
||||
echo $line . PHP_EOL;
|
||||
}
|
||||
echo "Fertig: {$result['count']} Ticket(s) anonymisiert." . PHP_EOL;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Führt die Anonymisierung aus.
|
||||
* Kann von settings.php (manuell) und vom Cron aufgerufen werden.
|
||||
*
|
||||
* @return array{count: int, log: string[]}
|
||||
*/
|
||||
function run_anonymization(PDO $pdo, bool $verbose = false): array
|
||||
{
|
||||
$result = ['count' => 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.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user