commit 29a2c9eb0293bc35aa10169a67a950e7f5aa359a Author: joel Date: Fri Jul 17 18:31:58 2026 +0200 upload diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..0faaae6 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..866aed9 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# Event Bilder Upload + +

+ App Preview +

+ +Ein einfacher, moderner und responsiver Web-Service zum datumsgenauen Hochladen von Bildern für beliebige Events. + +## Organisatorische Details + +- **Zweck**: Sammeln von Bildern von Teilnehmern eines Events. +- **Limitierungen**: + - Maximale Dateigröße pro Bild: 15 MB + - Erlaubte Formate: JPG, JPEG, PNG, GIF, WEBP, TIFF, HEIC +- **Deadline**: Ein Upload-Zeitraum kann festgelegt werden. Nach Ablauf der Deadline sind keine Uploads mehr möglich. Der Countdown wird live auf der Seite angezeigt. +- **Dateibenennung**: Die hochgeladenen Dateien werden serverseitig automatisch umbenannt. Das Format ist `Datum - Beschreibung.Endung`. Das Datum wird dabei intelligent aus den EXIF-Daten (Erstellungsdatum) des Bildes ausgelesen. +- **Speicherort**: Alle Bilder werden im Unterordner `images/` abgelegt. + +## Technische Details + +- **Technologie-Stack**: + - Backend: PHP (Einzelne Datei `index.php`) + - Frontend: HTML5, Vanilla JavaScript, Vanilla CSS +- **Architektur**: + - Keine Datenbank erforderlich. + - Asynchrone Uploads via AJAX inklusive Fortschrittsbalken und Einzelstatus pro Bild. + - Client-seitige und Server-seitige Validierung (Dateityp, Größe, Deadline). +- **Abhängigkeiten**: + - Webserver (z. B. Nginx, Apache oder Lighttpd) + - PHP 8.x + - PHP-Erweiterungen: `session`, `json`, `exif` (für das Auslesen des Aufnahmedatums), `mbstring` + +## Konfiguration + +Das Skript ist neutral gestaltet und kann für jedes beliebige Event genutzt werden. Alle Anpassungen können direkt im Kopfbereich der Datei `index.php` vorgenommen werden. Es sind keine weiteren Änderungen im HTML-Code nötig. + +```php +// EVENT CONFIGURATION +const EVENT_NAME = 'Event Bilder Upload'; +const EVENT_SUBTITLE = 'Speichere deine Momente datumsgerecht.'; + +// DEADLINE CONFIGURATION +// Format: YYYY-MM-DD HH:MM:SS +const UPLOAD_DEADLINE = '2026-11-30 23:59:59'; +``` + +## Installation auf einem frischen Alpine Linux + +Die folgende Anleitung beschreibt die schnelle Einrichtung auf einem frisch installierten Alpine Linux System unter Verwendung des integrierten PHP-Webservers. + +1. **System aktualisieren**: + ```sh + apk update + apk upgrade + ``` + +2. **PHP und benötigte Erweiterungen installieren**: + ```sh + apk add php php-json php-exif php-mbstring php-session + ``` + +3. **Projektdateien bereitstellen**: + Legen Sie die Datei `index.php` in das gewünschte Verzeichnis auf dem Server (z. B. `/var/www/imgsender`). + +4. **Verzeichnisrechte anpassen**: + Damit das Skript den Ordner `images/` anlegen und die hochgeladenen Bilder speichern kann, benötigt der ausführende Nutzer Schreibrechte in dem Verzeichnis. + ```sh + cd /var/www/imgsender + chmod 777 . + ``` + *(Hinweis: Für den dauerhaften produktiven Einsatz mit einem echten Webserver wie Nginx oder Lighttpd sollten die Rechte restriktiver auf den jeweiligen Webserver-Nutzer, z. B. `nobody`, angepasst werden: `chown -R nobody:nobody .`)* + +5. **Server starten**: + Für Testzwecke oder simple Deployments kann der integrierte PHP-Server genutzt werden: + ```sh + php -S 0.0.0.0:8080 -t . + ``` + Die Anwendung ist nun über den Browser unter `http://:8080` erreichbar. diff --git a/images/.gitignore b/images/.gitignore new file mode 100644 index 0000000..731a0a3 --- /dev/null +++ b/images/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +images/* diff --git a/images/17-07-2026 - Jugendtreff (1).jpg b/images/17-07-2026 - Jugendtreff (1).jpg new file mode 100644 index 0000000..980779c Binary files /dev/null and b/images/17-07-2026 - Jugendtreff (1).jpg differ diff --git a/images/17-07-2026 - Jugendtreff (1).png b/images/17-07-2026 - Jugendtreff (1).png new file mode 100644 index 0000000..28a5838 Binary files /dev/null and b/images/17-07-2026 - Jugendtreff (1).png differ diff --git a/images/17-07-2026 - Jugendtreff (2).jpg b/images/17-07-2026 - Jugendtreff (2).jpg new file mode 100644 index 0000000..8303f7d Binary files /dev/null and b/images/17-07-2026 - Jugendtreff (2).jpg differ diff --git a/images/17-07-2026 - Jugendtreff (3).jpg b/images/17-07-2026 - Jugendtreff (3).jpg new file mode 100644 index 0000000..0217371 Binary files /dev/null and b/images/17-07-2026 - Jugendtreff (3).jpg differ diff --git a/images/17-07-2026 - Jugendtreff.jpg b/images/17-07-2026 - Jugendtreff.jpg new file mode 100644 index 0000000..2ca681f Binary files /dev/null and b/images/17-07-2026 - Jugendtreff.jpg differ diff --git a/images/17-07-2026 - Jugendtreff.png b/images/17-07-2026 - Jugendtreff.png new file mode 100644 index 0000000..0012a8b Binary files /dev/null and b/images/17-07-2026 - Jugendtreff.png differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..4df67be --- /dev/null +++ b/index.php @@ -0,0 +1,819 @@ + - . + * - Date from EXIF "DateTimeOriginal" + * - Max size 15MB per file + * - Live Countdown & Progress Bar + * - Modern Dark UI + */ + +// EVENT CONFIGURATION +const EVENT_NAME = 'Event Bilder Upload'; +const EVENT_SUBTITLE = 'Speichere deine Momente datumsgerecht.'; + +// DEADLINE CONFIGURATION +// Format: YYYY-MM-DD HH:MM:SS +const UPLOAD_DEADLINE = '2026-11-30 23:59:59'; + +// Timezone +date_default_timezone_set('Europe/Berlin'); + +$messages = []; // Array to store status messages for each file + +// Maximum file size in bytes (15 MB) +const MAX_FILE_SIZE = 15 * 1024 * 1024; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + header('Content-Type: application/json'); + $responseMessages = []; + + // Check Deadline + if (time() > strtotime(UPLOAD_DEADLINE)) { + echo json_encode([ + 'status' => 'error', + 'messages' => [['type' => 'error', 'text' => 'Der Upload-Zeitraum ist abgelaufen.']] + ]); + exit; + } + + $targetDir = __DIR__ . "/images/"; + + // Ensure directory exists + if (!is_dir($targetDir)) { + if (!@mkdir($targetDir, 0755, true)) { + $errorInfo = error_get_last(); + $sysError = $errorInfo ? $errorInfo['message'] : 'Unbekannter Fehler'; + echo json_encode([ + 'status' => 'error', + 'messages' => [['type' => 'error', 'text' => "Server-Fehler: Konnte Verzeichnis 'images' nicht erstellen. ($sysError)"]] + ]); + exit; + } + } + + if (isset($_FILES['images'])) { + $fileCount = count($_FILES['images']['name']); + $descriptions = $_POST['descriptions'] ?? []; + + for ($i = 0; $i < $fileCount; $i++) { + $fileName = $_FILES['images']['name'][$i]; + $fileTmpPath = $_FILES['images']['tmp_name'][$i]; + $fileSize = $_FILES['images']['size'][$i]; + $fileError = $_FILES['images']['error'][$i]; + $description = trim($descriptions[$i] ?? ''); + + // Context prefix for messages + $msgPrefix = "$fileName: "; + + // Validate Description + $descriptionSafe = preg_replace('/[^a-zA-Z0-9\-_äöüÄÖÜß ]/u', '', $description); + $descriptionSafe = trim(preg_replace('/\s+/', ' ', $descriptionSafe)); + + if (empty($descriptionSafe)) { + $responseMessages[] = ['type' => 'error', 'text' => $msgPrefix . "Keine gültige Beschreibung."]; + continue; + } + + if ($fileError !== UPLOAD_ERR_OK) { + $errorMessages = [ + UPLOAD_ERR_INI_SIZE => 'Datei zu groß (server limit).', + UPLOAD_ERR_FORM_SIZE => 'Datei zu groß (form limit).', + UPLOAD_ERR_PARTIAL => 'Nur teilweise hochgeladen.', + UPLOAD_ERR_NO_FILE => 'Keine Datei.', + UPLOAD_ERR_NO_TMP_DIR => 'Temporärer Ordner fehlt.', + UPLOAD_ERR_CANT_WRITE => 'Schreibfehler.', + ]; + $err = $errorMessages[$fileError] ?? "Fehler Code $fileError"; + $responseMessages[] = ['type' => 'error', 'text' => $msgPrefix . $err]; + continue; + } + + if ($fileSize > MAX_FILE_SIZE) { + $responseMessages[] = ['type' => 'error', 'text' => $msgPrefix . "Größer als 15MB."]; + continue; + } + + // Extension Check + $fileNameCmps = explode(".", $fileName); + $fileExtension = strtolower(end($fileNameCmps)); + $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'heic']; + + if (!in_array($fileExtension, $allowedExtensions)) { + $responseMessages[] = ['type' => 'error', 'text' => $msgPrefix . "Format nicht erlaubt."]; + continue; + } + + // Exif Date + $dateStr = null; + if (function_exists('exif_read_data')) { + $exif = @exif_read_data($fileTmpPath); + if ($exif && !empty($exif['DateTimeOriginal'])) { + $dateTimeObj = DateTime::createFromFormat('Y:m:d H:i:s', $exif['DateTimeOriginal']); + if ($dateTimeObj) { + $dateStr = $dateTimeObj->format('d-m-Y'); + } + } + } + if (!$dateStr) { + $dateStr = date('d-m-Y'); + } + + // Construct Target Path + $newFileName = "{$dateStr} - {$descriptionSafe}.{$fileExtension}"; + $dest_path = $targetDir . $newFileName; + + // Handle Duplicates + $counter = 1; + $info = pathinfo($dest_path); + while (file_exists($dest_path)) { + $dest_path = $info['dirname'] . '/' . $info['filename'] . " ($counter)." . $info['extension']; + $newFileName = basename($dest_path); + $counter++; + } + + if (move_uploaded_file($fileTmpPath, $dest_path)) { + $responseMessages[] = ['type' => 'success', 'text' => $msgPrefix . "Gespeichert als \"$newFileName\"."]; + } else { + $errorInfo = error_get_last(); + $sysError = $errorInfo ? $errorInfo['message'] : 'Unbekannter Fehler'; + $responseMessages[] = ['type' => 'error', 'text' => $msgPrefix . "Upload gescheitert: $sysError"]; + } + } + } + + echo json_encode(['status' => 'done', 'messages' => $responseMessages]); + exit; +} +?> + + + + + + + <?= htmlspecialchars(EVENT_NAME) ?> + + + + + + + + +
+

+

+ + +
Lädt...
+ + +
+
+ + +
+ +
+ +
+ + +
+ Dateien wählen + oder per Drag & Drop (Max. 15MB/Bild) + +
+ + +
+ + +
+
+
+ + +
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..5415ab7 Binary files /dev/null and b/preview.png differ