M6: CSV-Import mit Vorschau, Dublettenpruefung und Audit-Trail
csvupload.php verarbeitete CSV-Zeilen bisher sofort beim Upload, ohne Vorschau, ohne Zugriffskontrolle (nur CSRF) und schrieb ausschliesslich in die global unscoped kl_Einzahlungen-Tabelle. - Neue Tabellen payment_import_batches/payment_import_rows protokollieren jeden Import: Datei, Pruefsumme, jede Zeile mit Rohwerten, erkanntem Mitglied, Status und erzeugter Ledger-Zeile. - Zweistufiger Ablauf: Hochladen zeigt nur eine Vorschau (matched/ duplicate/unmatched/invalid), erst "Import bestaetigen" bucht. - Zuordnung per paypal_name oder display_name (tenant-scoped), Dubletten- pruefung gegen bestehende nicht-stornierte Ledger-Zahlungen. - Buchung folgt dem etablierten Zweig-Muster: Default-Mandant per Dual-Write nach kl_Einzahlungen plus Spiegelung, alle anderen Mandanten direkt ueber ledger_record_payment(). - Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback). - Live getestet: Treffer, unbekannter Name, ungueltiger Betrag korrekt klassifiziert; Import bestaetigt mit korrektem Dual-Write; erneuter Upload derselben Datei erkennt die Zeile korrekt als Dublette. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+214
-251
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
include "functions.php";
|
||||
require_once __DIR__ . "/app/ledger.php";
|
||||
require_once __DIR__ . "/app/imports.php";
|
||||
app_require_csrf();
|
||||
include "header.php";
|
||||
include "headerline.php";
|
||||
@@ -14,254 +16,179 @@ include "nav.php";
|
||||
<div class="content">
|
||||
|
||||
<?php
|
||||
if(checkKaffeelisteAdmin($conn, $mailadress)){
|
||||
|
||||
function csv_h($value): string
|
||||
{
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
$pdo = app_db_pdo();
|
||||
$saasUser = saas_current_user($pdo);
|
||||
$tenantId = 0;
|
||||
$hasAccess = false;
|
||||
|
||||
if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser)) {
|
||||
$tenantId = (int)$saasUser['tenant_id'];
|
||||
$hasAccess = true;
|
||||
}
|
||||
|
||||
if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) {
|
||||
$tenant = ledger_fetch_default_tenant($pdo);
|
||||
if ($tenant !== null) {
|
||||
$tenantId = (int)$tenant['id'];
|
||||
$hasAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasAccess) {
|
||||
echo "<h2>Kein Zugriff</h2>";
|
||||
include "footer.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
function csv_upload_dir(): string
|
||||
{
|
||||
return APP_ROOT . '/var/uploads';
|
||||
return APP_ROOT . '/var/uploads';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: ?string, 1: ?string} [Pfad, Fehlermeldung]
|
||||
*/
|
||||
function csv_prepare_upload(array $file): array
|
||||
{
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
return [null, 'Fehler beim Hochladen der Datei.'];
|
||||
}
|
||||
|
||||
if (($file['size'] ?? 0) <= 0 || $file['size'] > 5 * 1024 * 1024) {
|
||||
return [null, 'Die CSV-Datei ist leer oder größer als 5 MB.'];
|
||||
}
|
||||
|
||||
$originalName = (string)($file['name'] ?? '');
|
||||
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
if ($extension !== 'csv') {
|
||||
return [null, 'Es sind nur CSV-Dateien erlaubt.'];
|
||||
}
|
||||
|
||||
$tmpName = (string)($file['tmp_name'] ?? '');
|
||||
if (!is_uploaded_file($tmpName)) {
|
||||
return [null, 'Die hochgeladene Datei konnte nicht verifiziert werden.'];
|
||||
}
|
||||
|
||||
if (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo ? finfo_file($finfo, $tmpName) : false;
|
||||
if ($finfo) {
|
||||
finfo_close($finfo);
|
||||
}
|
||||
|
||||
$allowedMimeTypes = [
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
'text/x-csv',
|
||||
'application/csv',
|
||||
'application/vnd.ms-excel',
|
||||
'application/octet-stream',
|
||||
];
|
||||
if (is_string($mime) && !in_array($mime, $allowedMimeTypes, true)) {
|
||||
return [null, 'Der Dateityp wurde nicht als CSV erkannt.'];
|
||||
}
|
||||
}
|
||||
|
||||
$uploadDir = csv_upload_dir();
|
||||
if (!is_dir($uploadDir)) {
|
||||
@mkdir($uploadDir, 0700, true);
|
||||
}
|
||||
if (!is_dir($uploadDir) || !is_writable($uploadDir)) {
|
||||
return [null, 'Der Upload-Ordner ist nicht beschreibbar.'];
|
||||
}
|
||||
|
||||
$targetFile = $uploadDir . '/paypal_' . date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.csv';
|
||||
if (!move_uploaded_file($tmpName, $targetFile)) {
|
||||
return [null, 'Die CSV-Datei konnte nicht gespeichert werden.'];
|
||||
}
|
||||
|
||||
return [$targetFile, null];
|
||||
}
|
||||
|
||||
|
||||
// Funktion zum Überprüfen, ob der Mitarbeiter in der Tabelle vorhanden ist
|
||||
function isMitarbeiterExist($conn, $name)
|
||||
{
|
||||
$sql = "SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Name = ? or paypalname = ?";
|
||||
$params = array($name, $name);
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die(print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
return ($row !== false);
|
||||
}
|
||||
|
||||
// Funktion zum Überprüfen, ob ein identischer Eintrag bereits vorhanden ist
|
||||
function isDuplicateEntry($conn, $mitarbeiterID, $betrag, $datum)
|
||||
{
|
||||
// Um nur den gleichen Tag zu vergleichen, konvertieren wir den Datum-String zu einem DateTime-Objekt und verwenden die Funktion CONVERT
|
||||
$date = date_create($datum);
|
||||
if ($date === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$convertedDatum = date_format($date, 'Y-m-d');
|
||||
|
||||
#$sql = "SELECT count FROM dbo.kl_Einzahlungen WHERE MitarbeiterID = ? AND Betrag = ? AND CONVERT(VARCHAR, Datum, 23) = ?";
|
||||
$sql = "SELECT * FROM dbo.kl_Einzahlungen WHERE MitarbeiterID = ? AND Betrag = ? AND CONVERT(VARCHAR, Datum, 23) = ?";
|
||||
$params = array($mitarbeiterID, $betrag, $convertedDatum);
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die(print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
return $row !== null && (int)$row['MitarbeiterID'] > 0;
|
||||
}
|
||||
|
||||
// Funktion zum Verarbeiten der CSV-Datei und Rückgabe der Ergebnisse
|
||||
function processCSV($conn, $file)
|
||||
{
|
||||
$handle = fopen($file, "r");
|
||||
$success_entries = [];
|
||||
$failed_entries = [];
|
||||
|
||||
// Überspringe die erste Zeile (Header) der CSV-Datei
|
||||
fgetcsv($handle);
|
||||
|
||||
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
|
||||
if (count($data) < 8) {
|
||||
$failed_entries[] = array($data[0] ?? '', $data[3] ?? '', $data[7] ?? '', 4);
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $data[3]; // Index 3 entspricht dem "Name"-Feld in der CSV
|
||||
$betrag = $data[7]; // Index 7 entspricht dem "Brutto"-Feld in der CSV
|
||||
$betrag = str_replace(",", ".", $betrag);
|
||||
#echo "<br>";
|
||||
#$Ausgabe = array($data[0], $data[3], $data[7]);
|
||||
// Überprüfen, ob der Mitarbeiter existiert und der Betrag positiv ist
|
||||
if (isMitarbeiterExist($conn, $name) && $betrag > 0) {
|
||||
|
||||
$mitarbeiterID = getMitarbeiterID($conn, $name);
|
||||
$datum = $data[0]; // Index 0 entspricht dem "Datum"-Feld in der CSV
|
||||
|
||||
// Überprüfen, ob ein identischer Eintrag bereits vorhanden ist
|
||||
if (!isDuplicateEntry($conn, $mitarbeiterID, $betrag, $datum)) {
|
||||
|
||||
// Eintrag in die Tabelle dbo.kl_Einzahlungen einfügen
|
||||
$sql = "INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)";
|
||||
$params = array($mitarbeiterID, $betrag, $datum);
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt) {
|
||||
$success_entries[] = array($data[0], $data[3], $data[7], 0);
|
||||
} else {
|
||||
$failed_entries[] = array($data[0], $data[3], $data[7], 1);
|
||||
#die(print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
}else {
|
||||
#echo "Eintrag bereits vorhanden: " . print_r($data, true) . "\n";
|
||||
$failed_entries[] = array($data[0], $data[3], $data[7], 2);
|
||||
}
|
||||
|
||||
} else {
|
||||
$failed_entries[] = array($data[0], $data[3], $data[7], 3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return ['success' => $success_entries, 'failed' => $failed_entries];
|
||||
}
|
||||
|
||||
// Funktion zum Abrufen der MitarbeiterID
|
||||
function getMitarbeiterID($conn, $name)
|
||||
{
|
||||
$sql = "SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Name = ? or paypalname = ?";
|
||||
$params = array($name, $name);
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
|
||||
if ($stmt === false) {
|
||||
die(print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC))
|
||||
{
|
||||
return $row['MitarbeiterID'];
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
return [null, 'Fehler beim Hochladen der Datei.'];
|
||||
}
|
||||
#echo $mitarbeiterid;
|
||||
|
||||
|
||||
if (($file['size'] ?? 0) <= 0 || $file['size'] > 5 * 1024 * 1024) {
|
||||
return [null, 'Die CSV-Datei ist leer oder größer als 5 MB.'];
|
||||
}
|
||||
|
||||
$originalName = (string)($file['name'] ?? '');
|
||||
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
if ($extension !== 'csv') {
|
||||
return [null, 'Es sind nur CSV-Dateien erlaubt.'];
|
||||
}
|
||||
|
||||
$tmpName = (string)($file['tmp_name'] ?? '');
|
||||
if (!is_uploaded_file($tmpName)) {
|
||||
return [null, 'Die hochgeladene Datei konnte nicht verifiziert werden.'];
|
||||
}
|
||||
|
||||
if (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo ? finfo_file($finfo, $tmpName) : false;
|
||||
if ($finfo) {
|
||||
finfo_close($finfo);
|
||||
}
|
||||
|
||||
$allowedMimeTypes = [
|
||||
'text/plain', 'text/csv', 'text/x-csv', 'application/csv',
|
||||
'application/vnd.ms-excel', 'application/octet-stream',
|
||||
];
|
||||
if (is_string($mime) && !in_array($mime, $allowedMimeTypes, true)) {
|
||||
return [null, 'Der Dateityp wurde nicht als CSV erkannt.'];
|
||||
}
|
||||
}
|
||||
|
||||
$uploadDir = csv_upload_dir();
|
||||
if (!is_dir($uploadDir)) {
|
||||
@mkdir($uploadDir, 0700, true);
|
||||
}
|
||||
if (!is_dir($uploadDir) || !is_writable($uploadDir)) {
|
||||
return [null, 'Der Upload-Ordner ist nicht beschreibbar.'];
|
||||
}
|
||||
|
||||
$targetFile = $uploadDir . '/paypal_' . date('Ymd_His') . '_' . bin2hex(random_bytes(8)) . '.csv';
|
||||
if (!move_uploaded_file($tmpName, $targetFile)) {
|
||||
return [null, 'Die CSV-Datei konnte nicht gespeichert werden.'];
|
||||
}
|
||||
|
||||
return [$targetFile, null];
|
||||
}
|
||||
|
||||
// Überprüfen, ob das Formular abgeschickt wurde
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
// Überprüfen, ob eine Datei hochgeladen wurde
|
||||
if (isset($_FILES["csv_file"])) {
|
||||
[$csvFile, $uploadError] = csv_prepare_upload($_FILES["csv_file"]);
|
||||
|
||||
if ($uploadError !== null) {
|
||||
echo csv_h($uploadError);
|
||||
} else {
|
||||
// CSV-Datei verarbeiten
|
||||
$result = processCSV($conn, $csvFile);
|
||||
@unlink($csvFile);
|
||||
|
||||
echo "CSV-Datei erfolgreich verarbeitet.\n";
|
||||
|
||||
echo "<h3>Auswertung</h3>";
|
||||
echo "<table>
|
||||
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Datum</th>
|
||||
<th>Betrag</th>
|
||||
<th>Ergebnis</th>
|
||||
</tr>";
|
||||
|
||||
foreach ($result['success'] as $eintrag){
|
||||
echo "<tr>
|
||||
<td>" . csv_h($eintrag[0]) . "</td>
|
||||
<td>" . csv_h($eintrag[1]) . "</td>
|
||||
<td>" . csv_h($eintrag[2]) . "</td>
|
||||
<td>Erfolgreich gespeichert</td>
|
||||
</tr>";
|
||||
}
|
||||
foreach ($result['failed'] as $eintrag){
|
||||
echo "<tr>
|
||||
<td>" . csv_h($eintrag[0]) . "</td>
|
||||
<td>" . csv_h($eintrag[1]) . "</td>
|
||||
<td>" . csv_h($eintrag[2]) . "</td>
|
||||
<td>";
|
||||
if($eintrag[3] == 1){
|
||||
echo "SQL Fehler";
|
||||
}elseif($eintrag[3] == 2){
|
||||
echo "Eintrag schon vorhanden";
|
||||
}elseif($eintrag[3] == 3){
|
||||
echo "Benutzer nicht gefunden";
|
||||
}elseif($eintrag[3] == 4){
|
||||
echo "Ungültige CSV-Zeile";
|
||||
}
|
||||
|
||||
echo "</td>
|
||||
</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo "Fehler beim Hochladen der Datei.";
|
||||
}
|
||||
function csv_status_label(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'matched' => 'Wird importiert',
|
||||
'duplicate' => 'Bereits vorhanden',
|
||||
'unmatched' => 'Mitglied nicht gefunden',
|
||||
'invalid' => 'Ungültige Zeile',
|
||||
'imported' => 'Importiert',
|
||||
default => $status,
|
||||
};
|
||||
}
|
||||
|
||||
$meldung = null;
|
||||
$fehler = null;
|
||||
$vorschauBatchId = null;
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$aktion = $_POST['aktion'] ?? 'hochladen';
|
||||
|
||||
if ($aktion === 'hochladen' && isset($_FILES["csv_file"])) {
|
||||
[$csvFile, $uploadError] = csv_prepare_upload($_FILES["csv_file"]);
|
||||
|
||||
if ($uploadError !== null) {
|
||||
$fehler = $uploadError;
|
||||
} else {
|
||||
try {
|
||||
$checksum = hash_file('sha256', $csvFile);
|
||||
$rows = imports_parse_csv($csvFile);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
$batchId = imports_create_batch(
|
||||
$pdo,
|
||||
$tenantId,
|
||||
basename((string)$_FILES["csv_file"]["name"]),
|
||||
(string)$checksum,
|
||||
$saasUser['user_id'] ?? null
|
||||
);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$amountCents = imports_normalize_amount($row['amount_raw']);
|
||||
$timestamp = $row['date_raw'] !== '' ? strtotime($row['date_raw']) : false;
|
||||
|
||||
if ($amountCents === null || $timestamp === false) {
|
||||
imports_store_row($pdo, $batchId, $row['row_number'], null, $row['raw_name'], $amountCents ?? 0, date('Y-m-d H:i:s', $timestamp ?: time()), 'invalid', $row['raw']);
|
||||
continue;
|
||||
}
|
||||
|
||||
$bookedAt = date('Y-m-d H:i:s', $timestamp);
|
||||
$participant = imports_find_participant($pdo, $tenantId, $row['raw_name']);
|
||||
|
||||
if ($participant === null) {
|
||||
imports_store_row($pdo, $batchId, $row['row_number'], null, $row['raw_name'], $amountCents, $bookedAt, 'unmatched', $row['raw']);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (imports_is_duplicate($pdo, $tenantId, $participant['participant_id'], $amountCents, date('Y-m-d', $timestamp))) {
|
||||
imports_store_row($pdo, $batchId, $row['row_number'], $participant['participant_id'], $row['raw_name'], $amountCents, $bookedAt, 'duplicate', $row['raw']);
|
||||
continue;
|
||||
}
|
||||
|
||||
imports_store_row($pdo, $batchId, $row['row_number'], $participant['participant_id'], $row['raw_name'], $amountCents, $bookedAt, 'matched', $row['raw']);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$vorschauBatchId = $batchId;
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
$fehler = 'Die CSV-Datei konnte nicht verarbeitet werden.';
|
||||
} finally {
|
||||
@unlink($csvFile);
|
||||
}
|
||||
}
|
||||
} elseif ($aktion === 'importieren') {
|
||||
$batchId = (int)($_POST['batch_id'] ?? 0);
|
||||
try {
|
||||
$ergebnis = imports_commit_batch($pdo, $tenantId, $batchId);
|
||||
$meldung = $ergebnis['imported'] . ' Einzahlungen wurden importiert.';
|
||||
} catch (Throwable $e) {
|
||||
$fehler = $e->getMessage();
|
||||
$vorschauBatchId = $batchId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vorschau = $vorschauBatchId !== null ? imports_fetch_batch($pdo, $tenantId, $vorschauBatchId) : null;
|
||||
|
||||
?>
|
||||
|
||||
@@ -273,26 +200,62 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
<title>CSV Verarbeitung</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="post" enctype="multipart/form-data">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
<label for="csv_file">CSV-Datei auswählen:</label>
|
||||
<input type="file" name="csv_file" accept=".csv" required>
|
||||
<button type="submit">Datei hochladen</button>
|
||||
</form>
|
||||
|
||||
<h2>CSV-Import</h2>
|
||||
|
||||
<?php if ($meldung !== null): ?>
|
||||
<div class="hint-box success"><p><?php echo saas_html($meldung); ?></p></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($fehler !== null): ?>
|
||||
<div class="hint-box error"><p><?php echo saas_html($fehler); ?></p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($vorschau !== null): ?>
|
||||
<h3>Vorschau: <?php echo saas_html($vorschau['batch']['original_filename']); ?></h3>
|
||||
<p>Status: <?php echo saas_html($vorschau['batch']['status']); ?></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Zeile</th>
|
||||
<th>Name (CSV)</th>
|
||||
<th>Zugeordnetes Mitglied</th>
|
||||
<th>Betrag</th>
|
||||
<th>Datum</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
<?php foreach ($vorschau['rows'] as $row): ?>
|
||||
<tr>
|
||||
<td><?php echo (int)$row['row_num']; ?></td>
|
||||
<td><?php echo saas_html($row['raw_name']); ?></td>
|
||||
<td><?php echo saas_html($row['display_name'] ?? '—'); ?></td>
|
||||
<td><?php echo saas_html(number_format(((int)$row['amount_cents']) / 100, 2, ',', '.')); ?> €</td>
|
||||
<td><?php echo saas_html($row['booked_at']); ?></td>
|
||||
<td><?php echo saas_html(csv_status_label($row['status'])); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<?php if ($vorschau['batch']['status'] === 'previewed'): ?>
|
||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
<input type="hidden" name="aktion" value="importieren">
|
||||
<input type="hidden" name="batch_id" value="<?php echo (int)$vorschau['batch']['id']; ?>">
|
||||
<button type="submit">Import bestätigen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="post" enctype="multipart/form-data">
|
||||
<?php echo app_csrf_field(); ?>
|
||||
<input type="hidden" name="aktion" value="hochladen">
|
||||
<label for="csv_file">CSV-Datei auswählen:</label>
|
||||
<input type="file" name="csv_file" accept=".csv" required>
|
||||
<button type="submit">Datei hochladen</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
|
||||
}else{
|
||||
echo "<h2>Kein Zugriff</h2>";
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user