0) ob_end_clean(); header('Content-Type: application/json'); echo json_encode(['ok' => false, 'error' => $err['message'] . ' in ' . basename($err['file']) . ':' . $err['line']]); } }); } @ini_set('memory_limit', '1024M'); @set_time_limit(300); function import_json(array $data): never { while (ob_get_level() > 0) ob_end_clean(); header('Content-Type: application/json'); echo json_encode($data, JSON_INVALID_UTF8_SUBSTITUTE); exit; } function import_ajax_guard(): void { header('Content-Type: application/json'); if (!hash_equals($_SESSION['csrf_token'] ?? '', (string) ($_POST['csrf_token'] ?? ''))) import_json(['ok' => false, 'error' => 'CSRF']); } $stateDir = osticket_state_dir(); // ── Chunked Upload ───────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'upload_chunk') { import_ajax_guard(); $chunk = $_FILES['chunk'] ?? null; $idx = (int) ($_POST['chunk_index'] ?? 0); $total = (int) ($_POST['total_chunks'] ?? 1); $tmpFile = $stateDir . '/upload.sql'; if (!$chunk || $chunk['error'] !== UPLOAD_ERR_OK) import_json(['ok' => false, 'error' => 'Chunk-Fehler']); $fh = fopen($tmpFile, $idx === 0 ? 'wb' : 'ab'); fwrite($fh, file_get_contents($chunk['tmp_name'])); fclose($fh); $done = ($idx + 1) >= $total; if ($done) { $_SESSION['import_sql_path'] = $tmpFile; $_SESSION['import_filename'] = basename((string) ($_POST['filename'] ?? 'import.sql')); @unlink($stateDir . '/state.json'); @unlink($stateDir . '/entries.jsonl'); @unlink($stateDir . '/entry_map.jsonl'); } import_json(['ok' => true, 'done' => $done, 'chunk' => $idx]); } // ── Dateipfad ────────────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'use_path') { verify_csrf(); $filename = basename(trim($_POST['sql_filename'] ?? '')); $fullPath = realpath(__DIR__ . '/../' . $filename); $rootDir = realpath(__DIR__ . '/..'); if (!$filename || !$fullPath || strpos($fullPath, $rootDir) !== 0 || !file_exists($fullPath)) { flash('error', 'Datei nicht gefunden: ' . $filename); redirect('/staff/import_osticket.php'); } $_SESSION['import_sql_path'] = $fullPath; $_SESSION['import_filename'] = $filename; foreach (['state.json', 'entries.jsonl', 'entry_map.jsonl'] as $f) @unlink($stateDir . '/' . $f); redirect('/staff/import_osticket.php?step=analyze'); } // ── Analyse ──────────────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'analyze_chunk') { import_ajax_guard(); $tmp = $_SESSION['import_sql_path'] ?? ''; if (!$tmp || !file_exists($tmp)) import_json(['ok' => false, 'error' => 'Datei fehlt']); try { $res = osticket_analyze_step($tmp, $stateDir, (int) ($_POST['byte_offset'] ?? 0)); } catch (Throwable $e) { import_json(['ok' => false, 'error' => $e->getMessage()]); } import_json(['ok' => true, 'done' => $res['done'], 'byte_offset' => $res['offset'], 'file_size' => filesize($tmp), 'counts' => $res['counts']]); } // ── Import starten ───────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'start') { verify_csrf(); $allowed = ['users', 'structure', 'tickets', 'threads', 'attachments']; $_SESSION['import_what'] = array_values(array_intersect($allowed, (array) ($_POST['import'] ?? []))); redirect('/staff/import_osticket.php?step=run'); } // ── Reset ────────────────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'reset_import') { import_ajax_guard(); $type = $_POST['reset_type'] ?? 'all'; if ($type === 'threads_only') { $pdo->exec('DELETE FROM ticket_attachments'); $pdo->exec('DELETE FROM ticket_threads'); } else { $pdo->exec('DELETE FROM ticket_attachments'); $pdo->exec('DELETE FROM ticket_threads'); $pdo->exec('DELETE FROM ticket_ratings'); $pdo->exec('DELETE FROM tickets'); $pdo->exec('DELETE FROM users WHERE id NOT IN (SELECT DISTINCT user_id FROM tickets WHERE user_id IS NOT NULL)'); } @unlink($stateDir . '/entry_map.jsonl'); audit_log($staff['id'], 'Import-Reset: ' . $type); import_json(['ok' => true]); } // ── Import-Schritt ───────────────────────────────────────────────────────── if (($_POST['action'] ?? '') === 'import_chunk') { import_ajax_guard(); $pass = (string) ($_POST['pass'] ?? ''); $offset = (int) ($_POST['offset'] ?? 0); if (!in_array($pass, ['users', 'structure', 'tickets', 'threads', 'attachments'], true)) import_json(['ok' => false, 'error' => 'Unbekannter Durchgang']); if ($pass === 'threads' && $offset === 0) @unlink($stateDir . '/entry_map.jsonl'); try { $res = osticket_import_step($pdo, $stateDir, $pass, $offset, (string) ($_SESSION['import_sql_path'] ?? '')); } catch (Throwable $e) { import_json(['ok' => false, 'error' => $e->getMessage()]); } $importWhat = $_SESSION['import_what'] ?? []; if ($res['done'] && $pass === end($importWhat)) { $tmp = $_SESSION['import_sql_path'] ?? ''; if ($tmp && file_exists($tmp) && str_starts_with($tmp, $stateDir)) @unlink($tmp); osticket_state_clear($stateDir); unset($_SESSION['import_sql_path'], $_SESSION['import_what'], $_SESSION['import_filename']); audit_log($staff['id'], 'osTicket Import abgeschlossen'); } import_json(['ok' => true] + $res); } // ── Seite ────────────────────────────────────────────────────────────────── $step = $_GET['step'] ?? '1'; $sqlPath = $_SESSION['import_sql_path'] ?? ''; $filename = $_SESSION['import_filename'] ?? ''; $fileSize = $sqlPath && file_exists($sqlPath) ? filesize($sqlPath) : 0; $pageTitle = 'osTicket Import'; $activePage = 'settings'; require __DIR__ . '/_header.php'; ?>

osTicket Import

← Einstellungen
'Hochladen','analyze'=>'Analysieren','2'=>'Optionen','run'=>'Import'] as $s=>$l): ?>

SQL-Datei hochladen oder Pfad angeben

Unterstützt werden SQL-Dumps von osTicket 1.10 bis 1.18 (phpMyAdmin-Export oder mysqldump, auch mit anderem Tabellenpräfix als ost_). Importiert werden Kunden, Abteilungen, Hilfethemen, Tickets, Nachrichten und Anhänge.

Option 1: Datei in den DeskHub-Ordner legen (für große Dateien empfohlen)


Option 2: Direkt hochladen (in 5-MB-Teilen)

Analysiere: ( MB)

Liest die Datei in 4-MB-Blöcken. Bei sehr großen Dateien (Anhänge, Sitzungsdaten) kann das einige Minuten dauern.

Starte...
'Kunden','tickets'=>'Tickets','threads'=>'Nachrichten','departments'=>'Abteilungen','topics'=>'Hilfethemen','attachments'=>'Anhänge'] as $k=>$l): ?>
0

Was soll importiert werden?

'Kunden','tickets'=>'Tickets','threads'=>'Nachrichten','departments'=>'Abteilungen','topics'=>'Hilfethemen','attachments'=>'Anhänge'] as $k=>$l): ?>
['Kunden', 'Name und E-Mail-Adresse'], 'structure' => ['Abteilungen und Hilfethemen', 'Fehlende werden angelegt, gleichnamige vorhandene wiederverwendet. Tickets landen damit in der richtigen Abteilung.'], 'tickets' => ['Tickets', 'Betreff, Nummer, Status, Priorität, Abteilung, Hilfethema, zugewiesener Mitarbeiter (über gleiche E-Mail-Adresse), Zeitstempel. Gelöschte Tickets werden ausgelassen.'], 'threads' => ['Nachrichten', 'Kundennachrichten, Antworten, interne Notizen und Mitlesende'], 'attachments' => ['Anhänge (' . round($counts['attachment_bytes'] / 1048576, 1) . ' MB)', 'Dateien zu den Nachrichten, bis ' . round(OSTICKET_ATTACHMENT_MAX_BYTES / 1048576) . ' MB je Datei. Setzt die Nachrichten voraus.'], ] as $v => [$lb, $desc]): ?>

Der Import kann gefahrlos wiederholt werden: Vorhandene Kunden und Ticketnummern werden übersprungen, bereits importierte Nachrichten nicht doppelt angelegt. Vorher ein Backup erstellen ist trotzdem empfehlenswert.

Neu hochladen

Import läuft...

Seite nicht schließen.

'Kunden','structure'=>'Abteilungen und Hilfethemen','tickets'=>'Tickets','threads'=>'Nachrichten','attachments'=>'Anhänge'] as $p=>$l): if(!in_array($p,$importWhat)) continue; ?>
Wartend...