first commit

This commit is contained in:
2026-03-31 09:44:27 +02:00
commit 8dd4253ee3
123 changed files with 369814 additions and 0 deletions
+597
View File
@@ -0,0 +1,597 @@
<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
$loggedIn = isset($_SESSION['user_id']);
$pageTitle = 'Krater-Import - AstroTools';
$bodyClass = 'admin-page';
function h(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
function normalizeImportValue(?string $value): ?string
{
if ($value === null) {
return null;
}
$value = trim($value);
if ($value === '') {
return null;
}
$value = str_replace(["\xC2\xA0", "\u{00A0}"], ' ', $value);
if (function_exists('mb_check_encoding') && !mb_check_encoding($value, 'UTF-8')) {
$converted = @mb_convert_encoding($value, 'UTF-8', 'Windows-1252,ISO-8859-1,UTF-8');
if (is_string($converted) && $converted !== '') {
$value = $converted;
}
}
return trim($value);
}
function parseNullableFloat(?string $value): ?float
{
$value = normalizeImportValue($value);
if ($value === null) {
return null;
}
$value = str_replace(',', '.', $value);
return is_numeric($value) ? (float)$value : null;
}
function parseNullableInt(?string $value): ?int
{
$value = normalizeImportValue($value);
if ($value === null) {
return null;
}
return preg_match('/^-?\d+$/', $value) ? (int)$value : null;
}
function parseNullableDate(?string $value): ?string
{
$value = normalizeImportValue($value);
if ($value === null || stripos($value, 'not') !== false || $value === '?') {
return null;
}
$formats = ['d/m/Y H:i:s', 'd/m/Y', 'Y-m-d H:i:s', 'Y-m-d'];
foreach ($formats as $format) {
$dt = DateTimeImmutable::createFromFormat($format, $value);
if ($dt instanceof DateTimeImmutable) {
return $dt->format('Y-m-d');
}
}
return null;
}
function translateCatalogValue(string $field, ?string $value): ?string
{
$value = normalizeImportValue($value);
if ($value === null) {
return null;
}
static $maps = [
'iau_type' => [
'Crater' => 'Krater',
],
'type' => [
'Crater' => 'Krater',
],
'subtype' => [
'Not determined' => 'Nicht bestimmt',
],
'process' => [
'Meteoritic impact' => 'Meteoriteneinschlag',
],
'period' => [
'Formation period not found' => 'Entstehungszeit nicht bekannt',
'Pre-Nectarian (From -4.55 billions years to -3.92 billions years)' => 'Prä-Nektarisch (von -4,55 bis -3,92 Milliarden Jahre)',
'Nectarian (From -3.92 billions years to -3.85 billions years)' => 'Nektarisch (von -3,92 bis -3,85 Milliarden Jahre)',
'Imbrian (From -3.85 billions years to -3.2 billions years) ?' => 'Imbrisch (von -3,85 bis -3,2 Milliarden Jahre)?',
],
'geology' => [
'No details' => 'Keine Details',
],
'country' => [
'Germany' => 'Deutschland',
'Norway' => 'Norwegen',
'U. S. A.' => 'USA',
'Not appliable' => 'Nicht anwendbar',
],
'nationality' => [
'German' => 'Deutsch',
'Norwegian' => 'Norwegisch',
'American' => 'Amerikanisch',
'Not appliable' => 'Nicht anwendbar',
],
'face' => [
'Nearside' => 'Vorderseite',
'Farside' => 'Rückseite',
'Librations zone' => 'Librationszone',
],
'quadrant' => [
'North-East' => 'Nordost',
'South-East' => 'Südost',
'North-West' => 'Nordwest',
'South-West' => 'Südwest',
],
'interest_c' => [
'Low interest or difficult formation' => 'Geringes Interesse oder schwer beobachtbar',
'Pretty interesting formation' => 'Ziemlich interessante Formation',
'Not observable' => 'Nicht beobachtbar',
],
'moonday_s' => [
'Not observable' => 'Nicht beobachtbar',
'3 days after New Moon' => '3 Tage nach Neumond',
'2 days after New Moon' => '2 Tage nach Neumond',
],
'moonday_m' => [
'Not observable' => 'Nicht beobachtbar',
'2 days after Full Moon' => '2 Tage nach Vollmond',
'1 day after Full Moon' => '1 Tag nach Vollmond',
],
'diam_inst' => [
'Not observable' => 'Nicht beobachtbar',
'Naked eye' => 'Mit bloßem Auge',
],
'th_instru' => [
'Not observable' => 'Nicht beobachtbar',
'50 mm refractor' => '50-mm-Refraktor',
'10x binoculars' => '10x-Fernglas',
],
'pr_instru' => [
'Not observable' => 'Nicht beobachtbar',
'100 mm refractor' => '100-mm-Refraktor',
'10x binoculars' => '10x-Fernglas',
],
];
return $maps[$field][$value] ?? $value;
}
function createMoonCraterTableSql(): string
{
return "CREATE TABLE moon_craters (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
lun VARCHAR(64) DEFAULT NULL,
name_type VARCHAR(255) DEFAULT NULL,
iau_type VARCHAR(100) DEFAULT NULL,
iau_type_de VARCHAR(100) DEFAULT NULL,
type VARCHAR(100) DEFAULT NULL,
type_de VARCHAR(100) DEFAULT NULL,
subtype VARCHAR(150) DEFAULT NULL,
subtype_de VARCHAR(150) DEFAULT NULL,
process VARCHAR(150) DEFAULT NULL,
process_de VARCHAR(150) DEFAULT NULL,
period VARCHAR(255) DEFAULT NULL,
period_de VARCHAR(255) DEFAULT NULL,
period_source TEXT DEFAULT NULL,
geology VARCHAR(255) DEFAULT NULL,
geology_de VARCHAR(255) DEFAULT NULL,
name_detail VARCHAR(255) DEFAULT NULL,
name_origin VARCHAR(255) DEFAULT NULL,
iau_approval_date DATE DEFAULT NULL,
langrenus VARCHAR(255) DEFAULT NULL,
hevelius VARCHAR(255) DEFAULT NULL,
riccioli VARCHAR(255) DEFAULT NULL,
work VARCHAR(255) DEFAULT NULL,
country VARCHAR(255) DEFAULT NULL,
country_de VARCHAR(255) DEFAULT NULL,
nationality VARCHAR(255) DEFAULT NULL,
nationality_de VARCHAR(255) DEFAULT NULL,
century_n VARCHAR(50) DEFAULT NULL,
century_c VARCHAR(50) DEFAULT NULL,
birth_place VARCHAR(255) DEFAULT NULL,
birth_date VARCHAR(100) DEFAULT NULL,
death_place VARCHAR(255) DEFAULT NULL,
death_date VARCHAR(100) DEFAULT NULL,
facts TEXT DEFAULT NULL,
longi_n DECIMAL(10,6) DEFAULT NULL,
longi_n_360 DECIMAL(10,6) DEFAULT NULL,
longi_c VARCHAR(255) DEFAULT NULL,
lati_n DECIMAL(10,6) DEFAULT NULL,
lati_c VARCHAR(255) DEFAULT NULL,
face VARCHAR(100) DEFAULT NULL,
face_de VARCHAR(100) DEFAULT NULL,
quadrant VARCHAR(100) DEFAULT NULL,
quadrant_de VARCHAR(100) DEFAULT NULL,
area VARCHAR(255) DEFAULT NULL,
length_km DECIMAL(10,3) DEFAULT NULL,
wide_km DECIMAL(10,3) DEFAULT NULL,
length_arcsec DECIMAL(10,3) DEFAULT NULL,
height_m VARCHAR(255) DEFAULT NULL,
rapport VARCHAR(255) DEFAULT NULL,
general_1 TEXT DEFAULT NULL,
general_2 TEXT DEFAULT NULL,
slopes TEXT DEFAULT NULL,
walls TEXT DEFAULT NULL,
floor TEXT DEFAULT NULL,
interest_n INT DEFAULT NULL,
interest_c VARCHAR(255) DEFAULT NULL,
interest_c_de VARCHAR(255) DEFAULT NULL,
lunation VARCHAR(100) DEFAULT NULL,
moonday_s VARCHAR(255) DEFAULT NULL,
moonday_s_de VARCHAR(255) DEFAULT NULL,
moonday_m VARCHAR(255) DEFAULT NULL,
moonday_m_de VARCHAR(255) DEFAULT NULL,
diam_inst VARCHAR(255) DEFAULT NULL,
diam_inst_de VARCHAR(255) DEFAULT NULL,
th_instru VARCHAR(255) DEFAULT NULL,
th_instru_de VARCHAR(255) DEFAULT NULL,
pr_instru VARCHAR(255) DEFAULT NULL,
pr_instru_de VARCHAR(255) DEFAULT NULL,
source_row_json JSON DEFAULT NULL,
imported_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uniq_lun (lun),
KEY idx_name (name),
KEY idx_type (type),
KEY idx_face (face),
KEY idx_quadrant (quadrant),
KEY idx_longi_n_360 (longi_n_360),
KEY idx_lati_n (lati_n)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
}
function ensureMoonCraterTable(PDO $pdo): void
{
$tableExists = (bool)$pdo->query("SHOW TABLES LIKE 'moon_craters'")->fetchColumn();
if (!$tableExists) {
$pdo->exec(createMoonCraterTableSql());
return;
}
$columns = [];
$stmt = $pdo->query("SHOW COLUMNS FROM moon_craters");
foreach ($stmt->fetchAll() as $row) {
$columns[] = (string)($row['Field'] ?? '');
}
$requiredColumns = ['name', 'lun', 'iau_type', 'type', 'longi_n_360', 'lati_n'];
$missingColumns = array_values(array_diff($requiredColumns, $columns));
if ($missingColumns === []) {
return;
}
$backupTable = 'moon_craters_backup_' . date('Ymd_His');
$pdo->exec("RENAME TABLE moon_craters TO `" . $backupTable . "`");
$pdo->exec(createMoonCraterTableSql());
}
$report = null;
$defaultCsv = 'C:\Users\ich\Downloads\01_IAU_NAMED_EN.csv';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csvPath = trim((string)($_POST['csv_path'] ?? $defaultCsv));
$truncateFirst = isset($_POST['truncate_first']) && (string)$_POST['truncate_first'] === '1';
$uploadedLabel = null;
try {
if (
isset($_FILES['csv_upload']) &&
is_array($_FILES['csv_upload']) &&
(int)($_FILES['csv_upload']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE
) {
$uploadError = (int)($_FILES['csv_upload']['error'] ?? UPLOAD_ERR_OK);
if ($uploadError !== UPLOAD_ERR_OK) {
throw new RuntimeException('Datei-Upload fehlgeschlagen. Fehlercode: ' . $uploadError);
}
$tmpName = (string)($_FILES['csv_upload']['tmp_name'] ?? '');
if ($tmpName === '' || !is_uploaded_file($tmpName)) {
throw new RuntimeException('Die hochgeladene Datei ist ungueltig.');
}
$csvPath = $tmpName;
$uploadedLabel = (string)($_FILES['csv_upload']['name'] ?? 'hochgeladene Datei');
}
if (!is_file($csvPath)) {
throw new RuntimeException('CSV-Datei nicht gefunden: ' . $csvPath);
}
$config = require __DIR__ . '/../../config/database.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$config['host'],
$config['dbname'],
$config['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$config['user'],
$config['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
ensureMoonCraterTable($pdo);
if ($truncateFirst) {
$pdo->exec('TRUNCATE TABLE moon_craters');
}
$handle = fopen($csvPath, 'rb');
if ($handle === false) {
throw new RuntimeException('CSV-Datei konnte nicht geöffnet werden.');
}
$headers = fgetcsv($handle, 0, ';', '"', '\\');
if (!is_array($headers)) {
throw new RuntimeException('CSV-Kopfzeile konnte nicht gelesen werden.');
}
$headers = array_map(static fn($value) => strtoupper((string)$value), $headers);
$insertSql = "INSERT INTO moon_craters (
name, lun, name_type, iau_type, iau_type_de, type, type_de, subtype, subtype_de,
process, process_de, period, period_de, period_source, geology, geology_de,
name_detail, name_origin, iau_approval_date, langrenus, hevelius, riccioli, work,
country, country_de, nationality, nationality_de, century_n, century_c, birth_place,
birth_date, death_place, death_date, facts, longi_n, longi_n_360, longi_c, lati_n,
lati_c, face, face_de, quadrant, quadrant_de, area, length_km, wide_km, length_arcsec,
height_m, rapport, general_1, general_2, slopes, walls, floor, interest_n, interest_c,
interest_c_de, lunation, moonday_s, moonday_s_de, moonday_m, moonday_m_de, diam_inst,
diam_inst_de, th_instru, th_instru_de, pr_instru, pr_instru_de, source_row_json
) VALUES (
:name, :lun, :name_type, :iau_type, :iau_type_de, :type, :type_de, :subtype, :subtype_de,
:process, :process_de, :period, :period_de, :period_source, :geology, :geology_de,
:name_detail, :name_origin, :iau_approval_date, :langrenus, :hevelius, :riccioli, :work,
:country, :country_de, :nationality, :nationality_de, :century_n, :century_c, :birth_place,
:birth_date, :death_place, :death_date, :facts, :longi_n, :longi_n_360, :longi_c, :lati_n,
:lati_c, :face, :face_de, :quadrant, :quadrant_de, :area, :length_km, :wide_km, :length_arcsec,
:height_m, :rapport, :general_1, :general_2, :slopes, :walls, :floor, :interest_n, :interest_c,
:interest_c_de, :lunation, :moonday_s, :moonday_s_de, :moonday_m, :moonday_m_de, :diam_inst,
:diam_inst_de, :th_instru, :th_instru_de, :pr_instru, :pr_instru_de, :source_row_json
)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
name_type = VALUES(name_type),
iau_type = VALUES(iau_type),
iau_type_de = VALUES(iau_type_de),
type = VALUES(type),
type_de = VALUES(type_de),
subtype = VALUES(subtype),
subtype_de = VALUES(subtype_de),
process = VALUES(process),
process_de = VALUES(process_de),
period = VALUES(period),
period_de = VALUES(period_de),
period_source = VALUES(period_source),
geology = VALUES(geology),
geology_de = VALUES(geology_de),
name_detail = VALUES(name_detail),
name_origin = VALUES(name_origin),
iau_approval_date = VALUES(iau_approval_date),
langrenus = VALUES(langrenus),
hevelius = VALUES(hevelius),
riccioli = VALUES(riccioli),
work = VALUES(work),
country = VALUES(country),
country_de = VALUES(country_de),
nationality = VALUES(nationality),
nationality_de = VALUES(nationality_de),
century_n = VALUES(century_n),
century_c = VALUES(century_c),
birth_place = VALUES(birth_place),
birth_date = VALUES(birth_date),
death_place = VALUES(death_place),
death_date = VALUES(death_date),
facts = VALUES(facts),
longi_n = VALUES(longi_n),
longi_n_360 = VALUES(longi_n_360),
longi_c = VALUES(longi_c),
lati_n = VALUES(lati_n),
lati_c = VALUES(lati_c),
face = VALUES(face),
face_de = VALUES(face_de),
quadrant = VALUES(quadrant),
quadrant_de = VALUES(quadrant_de),
area = VALUES(area),
length_km = VALUES(length_km),
wide_km = VALUES(wide_km),
length_arcsec = VALUES(length_arcsec),
height_m = VALUES(height_m),
rapport = VALUES(rapport),
general_1 = VALUES(general_1),
general_2 = VALUES(general_2),
slopes = VALUES(slopes),
walls = VALUES(walls),
floor = VALUES(floor),
interest_n = VALUES(interest_n),
interest_c = VALUES(interest_c),
interest_c_de = VALUES(interest_c_de),
lunation = VALUES(lunation),
moonday_s = VALUES(moonday_s),
moonday_s_de = VALUES(moonday_s_de),
moonday_m = VALUES(moonday_m),
moonday_m_de = VALUES(moonday_m_de),
diam_inst = VALUES(diam_inst),
diam_inst_de = VALUES(diam_inst_de),
th_instru = VALUES(th_instru),
th_instru_de = VALUES(th_instru_de),
pr_instru = VALUES(pr_instru),
pr_instru_de = VALUES(pr_instru_de),
source_row_json = VALUES(source_row_json)";
$stmt = $pdo->prepare($insertSql);
$imported = 0;
$skipped = 0;
while (($row = fgetcsv($handle, 0, ';', '"', '\\')) !== false) {
if ($row === [null] || $row === false) {
continue;
}
$assoc = [];
foreach ($headers as $index => $header) {
$assoc[$header] = $row[$index] ?? null;
}
$name = normalizeImportValue($assoc['NAME'] ?? null);
if ($name === null) {
$skipped++;
continue;
}
$payload = [
'name' => $name,
'lun' => normalizeImportValue($assoc['LUN'] ?? null),
'name_type' => normalizeImportValue($assoc['NAME_TYPE'] ?? null),
'iau_type' => normalizeImportValue($assoc['IAU_TYPE'] ?? null),
'iau_type_de' => translateCatalogValue('iau_type', $assoc['IAU_TYPE'] ?? null),
'type' => normalizeImportValue($assoc['TYPE'] ?? null),
'type_de' => translateCatalogValue('type', $assoc['TYPE'] ?? null),
'subtype' => normalizeImportValue($assoc['SUBTYPE'] ?? null),
'subtype_de' => translateCatalogValue('subtype', $assoc['SUBTYPE'] ?? null),
'process' => normalizeImportValue($assoc['PROCESS'] ?? null),
'process_de' => translateCatalogValue('process', $assoc['PROCESS'] ?? null),
'period' => normalizeImportValue($assoc['PERIOD'] ?? null),
'period_de' => translateCatalogValue('period', $assoc['PERIOD'] ?? null),
'period_source' => normalizeImportValue($assoc['PERIOD_SOURCE'] ?? null),
'geology' => normalizeImportValue($assoc['GEOLOGY'] ?? null),
'geology_de' => translateCatalogValue('geology', $assoc['GEOLOGY'] ?? null),
'name_detail' => normalizeImportValue($assoc['NAME_DETAIL'] ?? null),
'name_origin' => normalizeImportValue($assoc['NAME_ORIGIN'] ?? null),
'iau_approval_date' => parseNullableDate($assoc['IAU_APPROVAL'] ?? null),
'langrenus' => normalizeImportValue($assoc['LANGRENUS'] ?? null),
'hevelius' => normalizeImportValue($assoc['HEVELIUS'] ?? null),
'riccioli' => normalizeImportValue($assoc['RICCIOLI'] ?? null),
'work' => normalizeImportValue($assoc['WORK'] ?? null),
'country' => normalizeImportValue($assoc['COUNTRY'] ?? null),
'country_de' => translateCatalogValue('country', $assoc['COUNTRY'] ?? null),
'nationality' => normalizeImportValue($assoc['NATIONLITY'] ?? null),
'nationality_de' => translateCatalogValue('nationality', $assoc['NATIONLITY'] ?? null),
'century_n' => normalizeImportValue($assoc['CENTURY_N'] ?? null),
'century_c' => normalizeImportValue($assoc['CENTURY_C'] ?? null),
'birth_place' => normalizeImportValue($assoc['BIRTH_PLACE'] ?? null),
'birth_date' => normalizeImportValue($assoc['BIRTH_DATE'] ?? null),
'death_place' => normalizeImportValue($assoc['DEATH_PLACE'] ?? null),
'death_date' => normalizeImportValue($assoc['DEATH_DATE'] ?? null),
'facts' => normalizeImportValue($assoc['FACTS'] ?? null),
'longi_n' => parseNullableFloat($assoc['LONGI_N'] ?? null),
'longi_n_360' => parseNullableFloat($assoc['LONGI_N_360'] ?? null),
'longi_c' => normalizeImportValue($assoc['LONGI_C'] ?? null),
'lati_n' => parseNullableFloat($assoc['LATI_N'] ?? null),
'lati_c' => normalizeImportValue($assoc['LATI_C'] ?? null),
'face' => normalizeImportValue($assoc['FACE'] ?? null),
'face_de' => translateCatalogValue('face', $assoc['FACE'] ?? null),
'quadrant' => normalizeImportValue($assoc['QUADRANT'] ?? null),
'quadrant_de' => translateCatalogValue('quadrant', $assoc['QUADRANT'] ?? null),
'area' => normalizeImportValue($assoc['AREA'] ?? null),
'length_km' => parseNullableFloat($assoc['LENGTH_KM'] ?? null),
'wide_km' => parseNullableFloat($assoc['WIDE_KM'] ?? null),
'length_arcsec' => parseNullableFloat($assoc['LENGTH_ARCSEC'] ?? null),
'height_m' => normalizeImportValue($assoc['HEIGHT_M'] ?? null),
'rapport' => normalizeImportValue($assoc['RAPPORT'] ?? null),
'general_1' => normalizeImportValue($assoc['GENERAL_1'] ?? null),
'general_2' => normalizeImportValue($assoc['GENERAL_2'] ?? null),
'slopes' => normalizeImportValue($assoc['SLOPES'] ?? null),
'walls' => normalizeImportValue($assoc['WALLS'] ?? null),
'floor' => normalizeImportValue($assoc['FLOOR'] ?? null),
'interest_n' => parseNullableInt($assoc['INTEREST_N'] ?? null),
'interest_c' => normalizeImportValue($assoc['INTEREST_C'] ?? null),
'interest_c_de' => translateCatalogValue('interest_c', $assoc['INTEREST_C'] ?? null),
'lunation' => normalizeImportValue($assoc['LUNATION'] ?? null),
'moonday_s' => normalizeImportValue($assoc['MOONDAY_S'] ?? null),
'moonday_s_de' => translateCatalogValue('moonday_s', $assoc['MOONDAY_S'] ?? null),
'moonday_m' => normalizeImportValue($assoc['MOONDAY_M'] ?? null),
'moonday_m_de' => translateCatalogValue('moonday_m', $assoc['MOONDAY_M'] ?? null),
'diam_inst' => normalizeImportValue($assoc['DIAM_INST'] ?? null),
'diam_inst_de' => translateCatalogValue('diam_inst', $assoc['DIAM_INST'] ?? null),
'th_instru' => normalizeImportValue($assoc['TH_INSTRU'] ?? null),
'th_instru_de' => translateCatalogValue('th_instru', $assoc['TH_INSTRU'] ?? null),
'pr_instru' => normalizeImportValue($assoc['PR_INSTRU'] ?? null),
'pr_instru_de' => translateCatalogValue('pr_instru', $assoc['PR_INSTRU'] ?? null),
'source_row_json' => json_encode($assoc, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
];
$stmt->execute($payload);
$imported++;
}
fclose($handle);
$report = [
'ok' => true,
'csv_path' => $csvPath,
'csv_label' => $uploadedLabel ?? $csvPath,
'imported' => $imported,
'skipped' => $skipped,
'truncate_first' => $truncateFirst,
];
} catch (Throwable $exception) {
$report = [
'ok' => false,
'error' => $exception->getMessage(),
];
}
}
require __DIR__ . '/../../public/header.php';
?>
<div class="container" style="max-width: 980px;">
<section class="card" style="margin-top: 1.25rem;">
<h1>Krater-Import</h1>
<p class="hint">Importiert die IAU-Kraterdaten aus der CSV in die Tabelle <code>moon_craters</code>. Die Tabelle wird bei Bedarf automatisch angelegt.</p>
<?php if (is_array($report)): ?>
<?php if ($report['ok'] ?? false): ?>
<div class="msg-success">
Import erfolgreich.<br>
Datei: <?= h((string)($report['csv_label'] ?? $report['csv_path'])) ?><br>
Importiert/Aktualisiert: <?= h((string)$report['imported']) ?><br>
Uebersprungen: <?= h((string)$report['skipped']) ?>
</div>
<?php else: ?>
<div class="msg-error">
Import fehlgeschlagen: <?= h((string)($report['error'] ?? 'Unbekannter Fehler')) ?>
</div>
<?php endif; ?>
<?php endif; ?>
<form method="post" enctype="multipart/form-data" class="form-stack" style="margin-top: 1rem;">
<div class="form-group">
<label for="csv_upload">CSV-Datei auswaehlen</label>
<input type="file" id="csv_upload" name="csv_upload" accept=".csv,text/csv">
</div>
<div class="form-group">
<label for="csv_path">Oder Server-Pfad zur CSV-Datei</label>
<input type="text" id="csv_path" name="csv_path" value="<?= h((string)($_POST['csv_path'] ?? $defaultCsv)) ?>">
</div>
<label class="form-checkbox">
<input type="checkbox" name="truncate_first" value="1"<?= isset($_POST['truncate_first']) ? ' checked' : '' ?>>
Tabelle vor dem Import leeren
</label>
<div>
<button type="submit" class="btn btn-primary">Import starten</button>
</div>
</form>
</section>
</div>
<?php require __DIR__ . '/../../public/footer.php'; ?>