verlangeAnmeldung(); Housekeeping::beiGelegenheit(); $format = Dateiformat::pruefe($this->text('format')); if ($format === null) { $this->render('verwaltung/formatwahl.php', [ 'titel' => 'Export', 'richtung' => 'Export', 'ziel' => '/verwaltung/export', 'einleitung' => 'In welchem Format soll die Datei entstehen? Danach folgt der Filter ' . 'nach Zeitraum, Konto, Kategorie und Klasse.', ]); return; } $filter = $this->filter(); $this->render('verwaltung/export.php', [ 'titel' => 'Export (' . Dateiformat::name($format) . ')', 'format' => $format, 'filter' => $filter, 'konten' => Konto::alle(), 'kategorien' => Kategorie::baumFlach(), 'klassen' => Klasse::alle(), // Wie viele Buchungen der Filter trifft, steht schon vor dem // Herunterladen da – sonst lädt man eine leere Datei und merkt es // erst in der Tabellenkalkulation. 'anzahl' => BuchungsExport::anzahl($filter), ]); } /** Erzeugt die Datei und schickt sie als Download. */ public function datei(): void { $this->verlangeAnmeldung(); $format = Dateiformat::pruefe($this->text('format')); if ($format === null) { View::redirect('/verwaltung/export', 'Kein Format gewählt.', 'fehler'); return; } try { $datei = BuchungsExport::datei($format, $this->filter()); } catch (\Throwable $e) { View::redirect( '/verwaltung/export?format=' . $format, 'Export fehlgeschlagen: ' . $e->getMessage(), 'fehler' ); return; } header('Content-Type: ' . $datei['typ']); header('Content-Disposition: attachment; filename="' . $datei['name'] . '"'); header('Content-Length: ' . strlen($datei['inhalt'])); // Ein Auszug der eigenen Finanzen gehört in keinen Zwischenspeicher. header('Cache-Control: no-store'); echo $datei['inhalt']; } /** * Filterwerte, gleichgültig ob sie aus der Adresse oder aus dem Formular * kommen. * * Das Formular sendet als POST: Bei vielen angehakten Kategorien wächst * eine GET-Adresse über das, was der Webserver annimmt – nginx antwortet * dann mit „414 Request-URI Too Large“ statt mit der Datei. Ein kurzer * Aufruf mit wenigen Parametern bleibt trotzdem als Adresse möglich, * deshalb zählen beide Quellen; POST sticht, weil es die frischere Eingabe * ist. * * @return array */ private function eingabe(): array { return $_POST + $_GET; } /** * @return array{von: string, bis: string, konten: list, * kategorien: list, klassen: list} */ private function filter(): array { $eingabe = $this->eingabe(); return [ 'von' => $this->datum($this->text('von')), 'bis' => $this->datum($this->text('bis')), 'konten' => $this->ids($eingabe['konten'] ?? null), 'kategorien' => $this->ids($eingabe['kategorien'] ?? null), 'klassen' => $this->ids($eingabe['klassen'] ?? null), ]; } /** Ein einzelner Textwert; ein Feld, das als Liste ankommt, gilt als leer. */ private function text(string $feld): string { $wert = $this->eingabe()[$feld] ?? ''; return is_string($wert) ? trim($wert) : ''; } /** @return list */ private function ids(mixed $werte): array { if (!is_array($werte)) { return []; } $ids = []; foreach ($werte as $wert) { if (is_scalar($wert) && (int)$wert > 0) { $ids[] = (int)$wert; } } return array_values(array_unique($ids)); } private function datum(string $wert): string { return preg_match('/^\d{4}-\d{2}-\d{2}$/', $wert) === 1 ? $wert : ''; } }