Compare commits
8 Commits
ae21c3c5d8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
091b2c8473 | ||
|
|
b9971e9997 | ||
|
|
b6f6575534 | ||
|
|
42541f5603 | ||
|
|
74509fbc77 | ||
|
|
a730b30d4d | ||
|
|
b07a2ee24d | ||
|
|
376bfdeafe |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
www/secret.php
|
www/secret.php
|
||||||
www/_user.php
|
www/_user.php
|
||||||
www/files/*
|
www/_files/*
|
||||||
!www/files/.gitkeep
|
!www/_files/.gitkeep
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -6,14 +6,14 @@ Dieses Projekt stellt eine einfache Verwaltungsoberfläche für digitale Visiten
|
|||||||
|
|
||||||
* **PHP/Apache**-Weboberfläche in <code>www/</code> mit Frontend (<code>card.php</code>, <code>download.php</code>) und Backend (<code>admin.php</code> plus <code>_sql.php</code>, <code>_func.php</code>, <code>_user.php</code>).
|
* **PHP/Apache**-Weboberfläche in <code>www/</code> mit Frontend (<code>card.php</code>, <code>download.php</code>) und Backend (<code>admin.php</code> plus <code>_sql.php</code>, <code>_func.php</code>, <code>_user.php</code>).
|
||||||
* **MariaDB** (Docker) hält Tabellen für Identitäten, Felder, UUIDs, Berechtigungen, Dateien und Loginversuche (<code>db/init/001_schema.sql</code>).
|
* **MariaDB** (Docker) hält Tabellen für Identitäten, Felder, UUIDs, Berechtigungen, Dateien und Loginversuche (<code>db/init/001_schema.sql</code>).
|
||||||
* **Docker Compose** startet Webserver und Datenbank; <code>www/</code> wird in den Apache-Container gemountet, <code>www/files/</code> spiegelt das Upload-Verzeichnis <code>/var/www/files/</code>.
|
* **Docker Compose** startet Webserver und Datenbank; <code>www/</code> wird in den Apache-Container gemountet, <code>www/_files/</code> spiegelt das Upload-Verzeichnis <code>/var/www/html/_files/</code>.
|
||||||
* **Uploadverzeichnis** <code>www/files/</code> liegt außerhalb des Document Roots und ist als <code>.gitkeep</code> im Repository abgelegt, damit Hochladungen später von <code>download.php</code> referenziert werden.
|
* **Uploadverzeichnis** <code>www/_files/</code> liegt außerhalb des Document Roots und ist als <code>.gitkeep</code> im Repository abgelegt, damit Hochladungen später von <code>download.php</code> referenziert werden.
|
||||||
|
|
||||||
## Installation & erster Start
|
## Installation & erster Start
|
||||||
|
|
||||||
1. Repository clonen.
|
1. Repository clonen.
|
||||||
2. <code>www/secret.php</code> vom Beispiel kopieren (<code>secret.php.example</code>) und Datenbankzugang konfigurieren, damit <code>_sql.php</code> sich verbinden kann.
|
2. <code>www/secret.php</code> vom Beispiel kopieren (<code>secret.php.example</code>) und Datenbankzugang konfigurieren, damit <code>_sql.php</code> sich verbinden kann.
|
||||||
3. <code>mkdir -p www/files</code> (oder <code>md www/files</code> unter Windows) ausführen und darauf achten, dass die <code>.gitkeep</code>-Datei vorhanden ist, damit der Ordner beim Containerstart genutzt wird.
|
3. <code>mkdir -p www/_files</code> (oder <code>md www/_files</code> unter Windows) ausführen und darauf achten, dass die <code>.gitkeep</code>-Datei vorhanden ist, damit der Ordner beim Containerstart genutzt wird.
|
||||||
4. <code>docker-compose up --build</code> im Projektverzeichnis ausführen, um Apache und MariaDB zu starten.
|
4. <code>docker-compose up --build</code> im Projektverzeichnis ausführen, um Apache und MariaDB zu starten.
|
||||||
5. <code>http://localhost/admin.php</code> öffnen; die Standardanmeldedaten stehen in <code>www/_user.php</code> (<code>admin</code> / <code>password</code>) und sollten sofort geändert werden.
|
5. <code>http://localhost/admin.php</code> öffnen; die Standardanmeldedaten stehen in <code>www/_user.php</code> (<code>admin</code> / <code>password</code>) und sollten sofort geändert werden.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Dieses Projekt stellt eine einfache Verwaltungsoberfläche für digitale Visiten
|
|||||||
* Login schützt das Dashboard; nach drei gescheiterten Versuchen wird eine IP für eine Stunde gesperrt (<code>admin_login_attempts</code>).
|
* Login schützt das Dashboard; nach drei gescheiterten Versuchen wird eine IP für eine Stunde gesperrt (<code>admin_login_attempts</code>).
|
||||||
* Das Dashboard zeigt alle Identitäten sowie UUID-Token. Neue Identitäten erstellt man über <code>?action=identity_create</code>.
|
* Das Dashboard zeigt alle Identitäten sowie UUID-Token. Neue Identitäten erstellt man über <code>?action=identity_create</code>.
|
||||||
* Jedes Identity kann beliebige Felder (<code>identity_fields</code>) enthalten. Der Typ kann <code>single</code>, <code>multi</code>, <code>file</code> oder <code>url</code> sein.
|
* Jedes Identity kann beliebige Felder (<code>identity_fields</code>) enthalten. Der Typ kann <code>single</code>, <code>multi</code>, <code>file</code> oder <code>url</code> sein.
|
||||||
* Dateien werden pro Identität über das neue Upload-Formular hinzugefügt und landen in <code>www/files/</code>. Der Upload speichert Dateiname, generierten Zwischennamen und MIME-Type in <code>files</code>.
|
* Dateien werden pro Identität über das neue Upload-Formular hinzugefügt und landen in <code>www/_files/</code>. Der Upload speichert Dateiname, generierten Zwischennamen und MIME-Type in <code>files</code>. Unter dem Formular zeigt das Admin-UI eine Liste der bereits hochgeladenen Dateien inkl. einer Löschoption.
|
||||||
* Wenn ein Feld auf Typ <code>file</code> gesetzt ist, zeigt die Auswahl nur Uploads der aktuellen Identität (per <code>files.identity_id</code>). Die ausgewählte Datei-ID wird im Feldwert gespeichert, damit später nur diese Datei angezeigt oder heruntergeladen wird.
|
* Wenn ein Feld auf Typ <code>file</code> gesetzt ist, zeigt die Auswahl nur Uploads der aktuellen Identität (per <code>files.identity_id</code>). Die ausgewählte Datei-ID wird im Feldwert gespeichert, damit später nur diese Datei angezeigt oder heruntergeladen wird.
|
||||||
* UUID-Token (<code>access_tokens</code>) verbinden eine Identität mit einer eindeutigen Zeichenfolge, können optional Auslaufdaten und Notizen erhalten und dürfen **nur einmal** existieren. Tokenrechte (<code>token_permissions</code>) bestimmen, welche Feldschlüssel Besucher:innen sehen.
|
* UUID-Token (<code>access_tokens</code>) verbinden eine Identität mit einer eindeutigen Zeichenfolge, können optional Auslaufdaten und Notizen erhalten und dürfen **nur einmal** existieren. Tokenrechte (<code>token_permissions</code>) bestimmen, welche Feldschlüssel Besucher:innen sehen.
|
||||||
|
|
||||||
@@ -48,13 +48,13 @@ Dieses Projekt stellt eine einfache Verwaltungsoberfläche für digitale Visiten
|
|||||||
* **UUIDs sind einmalige Zugriffstoken** und sollten nicht doppelt vergeben oder offen weitergegeben werden, wenn geschützte Daten hinterlegt sind.
|
* **UUIDs sind einmalige Zugriffstoken** und sollten nicht doppelt vergeben oder offen weitergegeben werden, wenn geschützte Daten hinterlegt sind.
|
||||||
* **Admin-Zugang**: Bitte <code>www/_user.php</code> durch eigene Credentials ersetzen oder ein robusteres Auth-Verfahren implementieren.
|
* **Admin-Zugang**: Bitte <code>www/_user.php</code> durch eigene Credentials ersetzen oder ein robusteres Auth-Verfahren implementieren.
|
||||||
* **Loginversuche** werden nach drei Fehlversuchen eine Stunde gesperrt (<code>registerFailedLogin</code>, <code>isIpLocked</code>, <code>clearLoginAttempts</code>).
|
* **Loginversuche** werden nach drei Fehlversuchen eine Stunde gesperrt (<code>registerFailedLogin</code>, <code>isIpLocked</code>, <code>clearLoginAttempts</code>).
|
||||||
* **Datei-Uploads** liegen im Verzeichnis <code>www/files/</code> (Container: <code>/var/www/files/</code>) und dürfen ausschließlich über <code>download.php</code> mit gültigem Token ausgeliefert werden. Der Ordner sollte nicht direkt vom Webserver referenziert werden.
|
* **Datei-Uploads** liegen im Verzeichnis <code>www/_files/</code> (Container: <code>/var/www/html/_files/</code>) und dürfen ausschließlich über <code>download.php</code> mit gültigem Token ausgeliefert werden. Der Ordner sollte nicht direkt vom Webserver referenziert werden.
|
||||||
|
|
||||||
## Weiterentwicklung & Pflege
|
## Weiterentwicklung & Pflege
|
||||||
|
|
||||||
1. **Stil & Branding**: Die Inline-CSS im <code>card.php</code>-Head kann durch ein eigenes Template ersetzt oder ausgelagert werden.
|
1. **Stil & Branding**: Die Inline-CSS im <code>card.php</code>-Head kann durch ein eigenes Template ersetzt oder ausgelagert werden.
|
||||||
2. **Dateien & Automatisierung**: UUIDs und zugehörige Dateien lassen sich per API oder QR-Code verteilen. Die Download-Logik kann um Freigabezeitfenster erweitert werden.
|
2. **Dateien & Automatisierung**: UUIDs und zugehörige Dateien lassen sich per API oder QR-Code verteilen. Die Download-Logik kann um Freigabezeitfenster erweitert werden.
|
||||||
3. **Tests & Validierung**: Neue Felder brauchen eventuell Frontend-Validierung; Dateiuploads sollten auf MIME-Type, Größe und erlaubte Endungen geprüft werden.
|
3. **Tests & Validierung**: Neue Felder brauchen eventuell Frontend-Validierung; Dateiuploads sollten auf MIME-Type, Größe und erlaubte Endungen geprüft werden.
|
||||||
4. **Deployment**: Docker Compose genügt lokal. In Produktion empfiehlt sich ein Reverse Proxy, HTTPS und regelmäßige Backups des MariaDB-Volumes (<code>db_data</code>). Auch <code>www/files/</code> sollte gesichert werden, da dort alle Uploads gespeichert werden.
|
4. **Deployment**: Docker Compose genügt lokal. In Produktion empfiehlt sich ein Reverse Proxy, HTTPS und regelmäßige Backups des MariaDB-Volumes (<code>db_data</code>). Auch <code>www/_files/</code> sollte gesichert werden, da dort alle Uploads gespeichert werden.
|
||||||
|
|
||||||
> Hinweis: UUIDs repräsentieren digitale Visitenkarten und sind **einmalig**. Jeder Besuch über eine UUID dokumentiert genau eine Karte, deren Sichtbarkeit über Tokenrechte und Datei-Zuordnungen kontrolliert wird.
|
> Hinweis: UUIDs repräsentieren digitale Visitenkarten und sind **einmalig**. Jeder Besuch über eine UUID dokumentiert genau eine Karte, deren Sichtbarkeit über Tokenrechte und Datei-Zuordnungen kontrolliert wird.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ services:
|
|||||||
- ./www:/var/www/html
|
- ./www:/var/www/html
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
command: ["sh", "-c", "mkdir -p /var/www/html/_files && chown -R www-data:www-data /var/www/html/_files && apache2-foreground"]
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mariadb:11
|
image: mariadb:11
|
||||||
|
|||||||
145
www/admin.php
145
www/admin.php
@@ -6,6 +6,11 @@ require '_user.php';
|
|||||||
session_start();
|
session_start();
|
||||||
$ip = $_SERVER['REMOTE_ADDR'];
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||||||
|
|
||||||
|
const MAX_UPLOAD_FILE_SIZE_MB = 32;
|
||||||
|
const MAX_UPLOAD_FILE_SIZE_BYTES = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
|
||||||
|
ini_set('upload_max_filesize', MAX_UPLOAD_FILE_SIZE_MB . 'M');
|
||||||
|
ini_set('post_max_size', (MAX_UPLOAD_FILE_SIZE_MB + 1) . 'M');
|
||||||
|
|
||||||
/* ─────────────────────────────
|
/* ─────────────────────────────
|
||||||
Security
|
Security
|
||||||
───────────────────────────── */
|
───────────────────────────── */
|
||||||
@@ -265,10 +270,30 @@ if ($action === 'identity_edit') {
|
|||||||
exit('Identität nicht gefunden');
|
exit('Identität nicht gefunden');
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileUploadErrors = [];
|
$uploadErrorLabel = function (int $error): string {
|
||||||
$fileUploadSuccess = 0;
|
return match ($error) {
|
||||||
|
UPLOAD_ERR_INI_SIZE => 'Datei überschreitet upload_max_filesize',
|
||||||
|
UPLOAD_ERR_FORM_SIZE => 'Datei überschreitet MAX_FILE_SIZE',
|
||||||
|
UPLOAD_ERR_PARTIAL => 'Datei wurde nur teilweise hochgeladen',
|
||||||
|
UPLOAD_ERR_NO_FILE => 'keine Datei ausgewählt',
|
||||||
|
UPLOAD_ERR_NO_TMP_DIR => 'temporäres Verzeichnis fehlt',
|
||||||
|
UPLOAD_ERR_CANT_WRITE => 'Datei konnte nicht geschrieben werden',
|
||||||
|
UPLOAD_ERR_EXTENSION => 'Upload durch eine Erweiterung abgebrochen',
|
||||||
|
default => 'unbekannter Upload-Fehler',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$fileUploadErrors = $_SESSION['fileUploadErrors'] ?? [];
|
||||||
|
unset($_SESSION['fileUploadErrors']);
|
||||||
|
$fileUploadSuccess = (int)($_SESSION['fileUploadSuccess'] ?? 0);
|
||||||
|
unset($_SESSION['fileUploadSuccess']);
|
||||||
|
$fileDeleteMessage = $_SESSION['fileDeleteMessage'] ?? '';
|
||||||
|
unset($_SESSION['fileDeleteMessage']);
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$fileUploadErrors = [];
|
||||||
|
$fileUploadSuccess = 0;
|
||||||
|
$fileDeleteMessage = '';
|
||||||
|
|
||||||
if (isset($_POST['upload_files'])) {
|
if (isset($_POST['upload_files'])) {
|
||||||
$filesInput = $_FILES['files'] ?? null;
|
$filesInput = $_FILES['files'] ?? null;
|
||||||
@@ -287,7 +312,7 @@ if ($action === 'identity_edit') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($filesInput && $hasSelection) {
|
if ($filesInput && $hasSelection) {
|
||||||
$uploadDir = __DIR__ . '/../files/';
|
$uploadDir = __DIR__ . '/_files/';
|
||||||
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
|
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
|
||||||
$fileUploadErrors[] = 'Upload-Verzeichnis kann nicht erstellt werden.';
|
$fileUploadErrors[] = 'Upload-Verzeichnis kann nicht erstellt werden.';
|
||||||
} else {
|
} else {
|
||||||
@@ -309,7 +334,11 @@ if ($action === 'identity_edit') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($error !== UPLOAD_ERR_OK) {
|
if ($error !== UPLOAD_ERR_OK) {
|
||||||
$fileUploadErrors[] = sprintf('Fehler beim Hochladen von %s.', $originalName);
|
$fileUploadErrors[] = sprintf(
|
||||||
|
'Fehler beim Hochladen von %s: %s.',
|
||||||
|
$originalName,
|
||||||
|
$uploadErrorLabel($error)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,8 +351,10 @@ if ($action === 'identity_edit') {
|
|||||||
if ($safeName === '') {
|
if ($safeName === '') {
|
||||||
$safeName = 'file';
|
$safeName = 'file';
|
||||||
}
|
}
|
||||||
$storedName = bin2hex(random_bytes(12)) . '_' . $safeName;
|
do {
|
||||||
$destination = $uploadDir . $storedName;
|
$storedName = bin2hex(random_bytes(12)) . '_' . $safeName;
|
||||||
|
$destination = $uploadDir . $storedName;
|
||||||
|
} while (is_file($destination));
|
||||||
|
|
||||||
if (!move_uploaded_file($tmpName, $destination)) {
|
if (!move_uploaded_file($tmpName, $destination)) {
|
||||||
$fileUploadErrors[] = sprintf('Speichern von %s fehlgeschlagen.', $originalName);
|
$fileUploadErrors[] = sprintf('Speichern von %s fehlgeschlagen.', $originalName);
|
||||||
@@ -353,6 +384,33 @@ if ($action === 'identity_edit') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['delete_file'])) {
|
||||||
|
$fileId = (int)($_POST['file_id'] ?? 0);
|
||||||
|
if ($fileId > 0) {
|
||||||
|
$file = $sql->single(
|
||||||
|
"SELECT stored_name, filename FROM files WHERE id = ? AND identity_id = ?",
|
||||||
|
"ii",
|
||||||
|
[$fileId, $id]
|
||||||
|
);
|
||||||
|
if ($file) {
|
||||||
|
$diskPath = __DIR__ . '/_files/' . $file['stored_name'];
|
||||||
|
if (is_file($diskPath)) {
|
||||||
|
@unlink($diskPath);
|
||||||
|
}
|
||||||
|
$sql->set(
|
||||||
|
"DELETE FROM files WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$fileId]
|
||||||
|
);
|
||||||
|
$fileDeleteMessage = sprintf('Datei "%s" gelöscht.', $file['filename']);
|
||||||
|
} else {
|
||||||
|
$fileUploadErrors[] = 'Datei nicht gefunden oder gehört nicht zu dieser Identität.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$fileUploadErrors[] = 'Ungültige Datei.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Identität umbenennen
|
// Identität umbenennen
|
||||||
if (isset($_POST['rename'])) {
|
if (isset($_POST['rename'])) {
|
||||||
$sql->set(
|
$sql->set(
|
||||||
@@ -404,6 +462,10 @@ if ($action === 'identity_edit') {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_SESSION['fileUploadErrors'] = $fileUploadErrors;
|
||||||
|
$_SESSION['fileUploadSuccess'] = $fileUploadSuccess;
|
||||||
|
$_SESSION['fileDeleteMessage'] = $fileDeleteMessage;
|
||||||
|
|
||||||
header("Location: admin.php?action=identity_edit&id=$id");
|
header("Location: admin.php?action=identity_edit&id=$id");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -422,6 +484,18 @@ if ($action === 'identity_edit') {
|
|||||||
if ($identityFiles === false) {
|
if ($identityFiles === false) {
|
||||||
$identityFiles = [];
|
$identityFiles = [];
|
||||||
}
|
}
|
||||||
|
$duplicateFilenameCounts = [];
|
||||||
|
foreach ($identityFiles as $file) {
|
||||||
|
$filename = $file['filename'] ?? '';
|
||||||
|
if ($filename === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$duplicateFilenameCounts[$filename] = ($duplicateFilenameCounts[$filename] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
$duplicateFileNames = array_keys(array_filter(
|
||||||
|
$duplicateFilenameCounts,
|
||||||
|
static fn (int $count): bool => $count > 1
|
||||||
|
));
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
@@ -523,9 +597,27 @@ if ($action === 'identity_edit') {
|
|||||||
|
|
||||||
<?php if (!empty($identityFiles)): ?>
|
<?php if (!empty($identityFiles)): ?>
|
||||||
<h3>Vorhandene Dateien</h3>
|
<h3>Vorhandene Dateien</h3>
|
||||||
|
<?php if ($fileDeleteMessage !== ''): ?>
|
||||||
|
<p style="color:#22c55e; margin-top:.25rem;"><?= htmlspecialchars($fileDeleteMessage) ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($duplicateFileNames)): ?>
|
||||||
|
<p style="color:#fbbf24; margin-top:.25rem;">
|
||||||
|
Dateien mit identischem Dateinamen bleiben getrennt und können einzeln gelöscht.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($identityFiles as $file): ?>
|
<?php foreach ($identityFiles as $file): ?>
|
||||||
<li><?= htmlspecialchars($file['filename']) ?> (ID <?= (int)$file['id'] ?>)</li>
|
<li>
|
||||||
|
<?= htmlspecialchars($file['filename']) ?> (ID <?= (int)$file['id'] ?>)
|
||||||
|
<?php if (!empty($duplicateFileNames) && in_array($file['filename'], $duplicateFileNames, true)): ?>
|
||||||
|
<span style="color:#fbbf24; margin-left:.5rem; font-size:.85rem;">mehrfach vorhanden</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="post" style="display:inline; margin-left:.75rem;">
|
||||||
|
<input type="hidden" name="file_id" value="<?= (int)$file['id'] ?>">
|
||||||
|
<button name="delete_file" type="submit"
|
||||||
|
onclick="return confirm('Datei wirklich löschen?')">Löschen</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -709,6 +801,26 @@ if ($action === 'uuid_edit') {
|
|||||||
"i",
|
"i",
|
||||||
[$token['identity_id']]
|
[$token['identity_id']]
|
||||||
);
|
);
|
||||||
|
$files = $sql->get(
|
||||||
|
"SELECT id, filename FROM files
|
||||||
|
WHERE identity_id = ?
|
||||||
|
AND (token_id IS NULL OR token_id = ?)
|
||||||
|
ORDER BY uploaded_at DESC",
|
||||||
|
"ii",
|
||||||
|
[$token['identity_id'], $token['id']]
|
||||||
|
);
|
||||||
|
if ($files === false) {
|
||||||
|
$files = [];
|
||||||
|
}
|
||||||
|
$fileLinks = array_filter(array_map(static function ($file) use ($uuid) {
|
||||||
|
if (empty($file['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'url' => '/download.php?id=' . (int)$file['id'] . '&uuid=' . urlencode($uuid),
|
||||||
|
'filename' => (string)($file['filename'] ?: 'Datei'),
|
||||||
|
];
|
||||||
|
}, $files));
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html><head><meta charset="utf-8"><title>UUID bearbeiten</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<html><head><meta charset="utf-8"><title>UUID bearbeiten</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
@@ -736,6 +848,20 @@ if ($action === 'uuid_edit') {
|
|||||||
<button>Speichern</button>
|
<button>Speichern</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<?php if (!empty($fileLinks)): ?>
|
||||||
|
<h3>Dateien</h3>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($fileLinks as $link): ?>
|
||||||
|
<li>
|
||||||
|
<a href="<?= htmlspecialchars($link['url']) ?>"
|
||||||
|
target="_blank" rel="noopener noreferrer">
|
||||||
|
<?= htmlspecialchars($link['filename']) ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<p><a href="admin.php">← zurück</a></p>
|
<p><a href="admin.php">← zurück</a></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
<?php
|
<?php
|
||||||
@@ -806,4 +932,9 @@ $identities = $sql->get("SELECT * FROM identities ORDER BY id DESC");
|
|||||||
</html>
|
</html>
|
||||||
<?php
|
<?php
|
||||||
//TODO einheitliches dunkles design
|
//TODO einheitliches dunkles design
|
||||||
|
|
||||||
|
//TODO option schaffen eine bestehende datei zu überschreiben ??? bzw prüfen ob das notwendig ist
|
||||||
|
|
||||||
|
//TODO anzeige von einer gelöschten oder leeren datei ausblenden, bei vollzogenem löschvorgang
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ require '_func.php';
|
|||||||
* Eingaben prüfen
|
* Eingaben prüfen
|
||||||
*/
|
*/
|
||||||
$fileId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
$fileId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
$uuid = $_GET['uuid'] ?? null;
|
$uuid = trim($_GET['uuid'] ?? '');
|
||||||
|
|
||||||
if ($fileId <= 0 || !$uuid) {
|
if ($fileId <= 0 || !$uuid) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
exit('Ungültige Anfrage');
|
exit('Ungültige Anfrage');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $uuid)) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('Ungültige Anfrage');
|
||||||
|
//TODO fehlgeschlagener zugriff als fehlerhafte anmeldung ansehen und bei wiederholung ip sperren
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token validieren
|
* Token validieren
|
||||||
*/
|
*/
|
||||||
@@ -51,10 +57,10 @@ if (!$file) {
|
|||||||
/**
|
/**
|
||||||
* Dateipfad
|
* Dateipfad
|
||||||
*/
|
*/
|
||||||
$basePath = '/var/www/files/';
|
$basePath = realpath(__DIR__ . '/_files');
|
||||||
$path = realpath($basePath . $file['stored_name']);
|
$path = $basePath ? realpath($basePath . '/' . $file['stored_name']) : null;
|
||||||
|
|
||||||
if (!$path || !str_starts_with($path, $basePath) || !is_file($path)) {
|
if (!$path || !$basePath || !str_starts_with($path, $basePath) || !is_file($path)) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
exit('Datei fehlt');
|
exit('Datei fehlt');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
This directory stores uploaded files outside the document root.
|
|
||||||
Reference in New Issue
Block a user