verlangeAnmeldung(); $this->render('verwaltung/import.php', [ 'titel' => 'QIF-Import', 'dateien' => $this->dateien(), 'verzeichnis' => $this->verzeichnisAnzeige(), 'ergebnis' => null, 'datei' => '', ]); } public function hochladen(): void { $this->verlangeAnmeldung(); $datei = $_FILES['qif'] ?? null; if (!is_array($datei) || ($datei['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { View::redirect('/verwaltung/import', $this->uploadFehler($datei['error'] ?? UPLOAD_ERR_NO_FILE), 'fehler'); return; } $name = self::sichererDateiname((string)$datei['name']); if ($name === null) { View::redirect('/verwaltung/import', 'Nur .QIF-Dateien können hochgeladen werden.', 'fehler'); return; } $ziel = $this->verzeichnis() . '/' . $name; if (!is_uploaded_file((string)$datei['tmp_name']) || !move_uploaded_file((string)$datei['tmp_name'], $ziel)) { View::redirect('/verwaltung/import', 'Die Datei konnte nicht abgelegt werden.', 'fehler'); return; } View::redirect('/verwaltung/import', 'Datei „' . $name . '“ hochgeladen.'); } public function ausfuehren(): void { $this->verlangeAnmeldung(); $name = self::sichererDateiname($this->post('datei')); if ($name === null) { View::redirect('/verwaltung/import', 'Keine Datei ausgewählt.', 'fehler'); return; } $pfad = $this->verzeichnis() . '/' . $name; if (!is_file($pfad)) { View::redirect('/verwaltung/import', 'Die Datei „' . $name . '“ gibt es nicht mehr.', 'fehler'); return; } $trockenlauf = $this->post('modus') !== 'import'; try { $parser = QifParser::ausDatei($pfad); $importer = new QifStammdatenImport($parser); $ergebnis = $importer->ausfuehren($trockenlauf); } catch (\Throwable $e) { View::redirect('/verwaltung/import', 'Import fehlgeschlagen: ' . $e->getMessage(), 'fehler'); return; } $this->render('verwaltung/import.php', [ 'titel' => 'QIF-Import', 'dateien' => $this->dateien(), 'verzeichnis' => $this->verzeichnisAnzeige(), 'ergebnis' => $ergebnis + ['abschnitte' => $parser->zusammenfassung()], 'datei' => $name, ]); } /** @param array $params */ public function loeschen(array $params): void { $this->verlangeAnmeldung(); $name = self::sichererDateiname($params['datei'] ?? ''); if ($name !== null) { $pfad = $this->verzeichnis() . '/' . $name; if (is_file($pfad)) { unlink($pfad); } } View::redirect('/verwaltung/import', 'Datei gelöscht.'); } /** * Nimmt nur den Dateinamen ohne Pfadanteile und lässt allein die erlaubten * Endungen durch – der Name landet ungeprüft in einer Pfadangabe. */ public static function sichererDateiname(string $eingabe): ?string { $name = basename(trim($eingabe)); if ($name === '' || $name === '.' || $name === '..' || str_contains($name, "\0")) { return null; } $endung = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if (!in_array($endung, self::ERLAUBTE_ENDUNGEN, true)) { return null; } // Alles außer Buchstaben, Ziffern, Punkt, Strich und Unterstrich fällt // weg; damit bleiben keine Sonderzeichen für die Shell oder das // Dateisystem übrig. $name = preg_replace('/[^A-Za-z0-9._-]/', '_', $name) ?? ''; return $name === '' ? null : $name; } /** @return list */ private function dateien(): array { $verzeichnis = $this->verzeichnis(); $dateien = []; foreach (glob($verzeichnis . '/*.[qQ][iI][fF]') ?: [] as $pfad) { $dateien[] = [ 'name' => basename($pfad), 'groesse' => (int)filesize($pfad), 'geaendert' => (int)filemtime($pfad), ]; } usort($dateien, static fn(array $a, array $b): int => $b['geaendert'] <=> $a['geaendert']); return $dateien; } private function verzeichnis(): string { $relativ = (string)Einstellung::get('import_qif_verzeichnis', 'data/import'); $pfad = dirname(__DIR__, 2) . '/' . trim($relativ, '/'); if (!is_dir($pfad)) { mkdir($pfad, 0o775, true); } return $pfad; } private function verzeichnisAnzeige(): string { return (string)Einstellung::get('import_qif_verzeichnis', 'data/import'); } private function uploadFehler(int $code): string { return match ($code) { UPLOAD_ERR_NO_FILE => 'Es wurde keine Datei ausgewählt.', UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Die Datei ist zu groß.', UPLOAD_ERR_PARTIAL => 'Die Datei wurde nur teilweise übertragen.', default => 'Der Upload ist fehlgeschlagen (Code ' . $code . ').', }; } }