298 lines
9.1 KiB
PHP
298 lines
9.1 KiB
PHP
<?php
|
|
/**
|
|
* @file upload.php
|
|
* @brief Behandelt den Dateiupload für Profilbilder der Benutzer.
|
|
*
|
|
* @details Diese Datei nimmt ein hochgeladenes Bild als POST-Request entgegen.
|
|
* Sie validiert den Upload-Fehlercode, stellt sicher, dass es sich um ein gültiges
|
|
* Bild handelt (JPEG oder PNG) und verschiebt die Datei in das entsprechende
|
|
* Assets-Verzeichnis. Abschließend wird der Pfad zum hochgeladenen Bild in der
|
|
* Datenbank des Benutzers gespeichert. Alle Fehler und Erfolgsmeldungen leiten
|
|
* auf die account.php mit einem entsprechenden URL-Parameter zurück.
|
|
*/
|
|
|
|
/**
|
|
* @brief Bindet die grundlegenden Konfigurationen und Bibliotheken ein.
|
|
*/
|
|
require_once __DIR__ . '/lib/bootstrap.php';
|
|
|
|
/**
|
|
* @brief Überprüft, ob der Benutzer angemeldet ist.
|
|
*
|
|
* @details Wenn keine Benutzer-ID in der Session vorhanden ist,
|
|
* wird der Benutzer sofort auf die Login-Seite umgeleitet und das Skript beendet.
|
|
*/
|
|
if (empty($_SESSION['user_id']))
|
|
{
|
|
header('Location: login.php');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var int $userId Die ID des aktuell angemeldeten Benutzers, ausgelesen aus der Session.
|
|
*/
|
|
$userId = (int)$_SESSION['user_id'];
|
|
|
|
/**
|
|
* @brief Überprüft, ob die Anfrage über die POST-Methode gesendet wurde.
|
|
*
|
|
* @details Auf diese Datei darf nur per HTTP POST zugegriffen werden (Formular-Upload).
|
|
* Andernfalls wird der Benutzer zur account.php umgeleitet.
|
|
*/
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST')
|
|
{
|
|
header('Location: account.php');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @brief Überprüft, ob das Feld 'uploadFile' im $_FILES Array gesetzt ist.
|
|
*
|
|
* @details Es muss eine Datei über das erwartete Formularfeld übertragen worden sein.
|
|
*/
|
|
if (!isset($_FILES['uploadFile']) || !is_array($_FILES['uploadFile']))
|
|
{
|
|
header('Location: account.php?upload=err');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var array $file Referenz auf die hochgeladene Datei im $_FILES Array.
|
|
*/
|
|
$file = $_FILES['uploadFile'];
|
|
|
|
/**
|
|
* @var int $fileError Ermittelt den PHP-Upload-Fehlercode (UPLOAD_ERR_OK, falls erfolgreich).
|
|
*/
|
|
$fileError = isset($file['error']) ? (int)$file['error'] : UPLOAD_ERR_NO_FILE;
|
|
|
|
/**
|
|
* @brief Überprüft, ob beim Upload ein Fehler aufgetreten ist.
|
|
*
|
|
* @details Wenn $fileError nicht UPLOAD_ERR_OK ist, wird abgebrochen, der Fehler ins
|
|
* Error-Log geschrieben und zur Account-Seite mit dem Fehlercode umgeleitet.
|
|
*/
|
|
if ($fileError !== UPLOAD_ERR_OK)
|
|
{
|
|
// Serverseitiges Log ist ok, aber kein Pfad/Interna im Browser
|
|
error_log('Upload: PHP upload error=' . $fileError);
|
|
header('Location: account.php?upload=err&code=php_' . $fileError);
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var string $tmp Der temporäre Dateipfad, an dem die hochgeladene Datei abgelegt wurde.
|
|
*/
|
|
// Basic Validierung
|
|
$tmp = isset($file['tmp_name']) ? (string)$file['tmp_name'] : '';
|
|
|
|
/**
|
|
* @brief Überprüft die Existenz und Gültigkeit der temporären Upload-Datei.
|
|
*
|
|
* @details Nutzt is_uploaded_file, um Exploits und Path-Traversal-Angriffe zu verhindern.
|
|
*/
|
|
if ($tmp === '' || !is_uploaded_file($tmp))
|
|
{
|
|
// Debug-Detail (tmp-Pfad) nicht loggen
|
|
error_log('Upload: tmp invalid');
|
|
header('Location: account.php?upload=err&code=tmp');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var array $allowedMimeToExt Zuordnung von erlaubten MIME-Types zu deren Dateiendungen.
|
|
*/
|
|
$allowedMimeToExt = [
|
|
'image/jpeg' => 'jpg',
|
|
'image/png' => 'png',
|
|
];
|
|
|
|
/**
|
|
* @var finfo $finfo PHP finfo-Instanz zur Bestimmung des tatsächlichen MIME-Types der Datei.
|
|
*/
|
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
|
|
/**
|
|
* @var string|false $mime Der ausgelesene MIME-Type der temporären Datei.
|
|
*/
|
|
$mime = $finfo->file($tmp);
|
|
|
|
/**
|
|
* @brief Validiert den MIME-Type.
|
|
*
|
|
* @details Erlaubt sind nur die in $allowedMimeToExt definierten Dateitypen (JPEG, PNG).
|
|
*/
|
|
if (!$mime || !isset($allowedMimeToExt[$mime]))
|
|
{
|
|
// Mime loggen ist ok (kein Secret), hilft bei Support
|
|
error_log('Upload: invalid mime=' . (string)$mime);
|
|
header('Location: account.php?upload=err&code=mime');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var string $ext Die zur validierten Datei passende Dateiendung.
|
|
*/
|
|
$ext = $allowedMimeToExt[$mime];
|
|
|
|
/**
|
|
* @var string $relativeTargetDir Relativer Pfad zum Verzeichnis der Profilbilder.
|
|
*/
|
|
// Zielordner: assets/images/profilePictures relativ zum Projekt (upload.php liegt im Webroot)
|
|
$relativeTargetDir = 'assets/images/profilePictures';
|
|
|
|
/**
|
|
* @var string $dirTargetDir Absoluter Pfad zum Zielordner, basierend auf der aktuellen Datei (__DIR__).
|
|
*/
|
|
// Kandidat 1: relativ zu __DIR__ (robust gegen VHost/Alias)
|
|
$dirTargetDir = rtrim(__DIR__, "\\/") . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relativeTargetDir);
|
|
|
|
/**
|
|
* @var string $documentRoot Dokumentenwurzel des Webservers (falls gesetzt).
|
|
*/
|
|
// Kandidat 2: relativ zu DOCUMENT_ROOT (nur wenn gesetzt)
|
|
$documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? (string)$_SERVER['DOCUMENT_ROOT'] : '';
|
|
|
|
/**
|
|
* @var string $docRootTrim Formatierter Dokumenten-Root-Pfad (ohne abschließende Slashes).
|
|
*/
|
|
$docRootTrim = rtrim($documentRoot, "\\/");
|
|
|
|
/**
|
|
* @var string $docTargetDir Absoluter Zielordner aus der Server-Variablen (DOCUMENT_ROOT).
|
|
*/
|
|
$docTargetDir = ($docRootTrim !== '')
|
|
? $docRootTrim . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relativeTargetDir)
|
|
: '';
|
|
|
|
/**
|
|
* @var string $targetDir Finaler Pfad, in den die Datei verschoben wird.
|
|
* @details Bevorzugt wird __DIR__. Falls dieses aus Systemgründen fehlschlägt und DOCUMENT_ROOT zur Verfügung steht, wird dieser verwendet.
|
|
*/
|
|
// Bevorzugt __DIR__. Falls __DIR__ aus irgendeinem Grund nicht ins Projekt zeigt, und DOCUMENT_ROOT plausibel ist, nutze DOCUMENT_ROOT.
|
|
$targetDir = $dirTargetDir;
|
|
if ($docTargetDir !== '' && !is_dir($dirTargetDir) && is_dir($docTargetDir))
|
|
{
|
|
$targetDir = $docTargetDir;
|
|
}
|
|
|
|
/**
|
|
* @brief Prüft, ob das Zielverzeichnis existiert und erstellt es gegebenenfalls.
|
|
*
|
|
* @details Nutzt mkdir() rekursiv, um fehlende Ordnerstrukturen zu generieren.
|
|
*/
|
|
if (!is_dir($targetDir))
|
|
{
|
|
$mkOk = @mkdir($targetDir, 0755, true);
|
|
if (!$mkOk)
|
|
{
|
|
$lastErr = error_get_last();
|
|
$lastErrMsg = (is_array($lastErr) && isset($lastErr['message'])) ? (string)$lastErr['message'] : 'unknown';
|
|
error_log('Upload: mkdir failed for ' . $targetDir . ' - ' . $lastErrMsg);
|
|
header('Location: account.php?upload=err&code=mkdir');
|
|
exit();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Prüft, ob in den Zielordner geschrieben werden darf.
|
|
*/
|
|
if (!is_writable($targetDir))
|
|
{
|
|
error_log('Upload: targetDir not writable: ' . $targetDir);
|
|
header('Location: account.php?upload=err&code=perm');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var string $timestamp Ein Zeitstempel zur Vergabe einmaliger Dateinamen.
|
|
*/
|
|
// Dateiname: user_<ID>_<Datum>.<ext>
|
|
// Format ist dateisystem-sicher (keine Doppelpunkte) und eindeutig genug.
|
|
$timestamp = gmdate('Ymd-His');
|
|
|
|
/**
|
|
* @var string $filename Der zu verwendende neue Name für die hochgeladene Datei.
|
|
*/
|
|
$filename = 'user_' . $userId . '_' . $timestamp . '.' . $ext;
|
|
|
|
/**
|
|
* @var string $targetPath Der genaue finale Pfad (inkl. Dateiname).
|
|
*/
|
|
$targetPath = rtrim($targetDir, "\\/") . DIRECTORY_SEPARATOR . $filename;
|
|
|
|
/**
|
|
* @brief Verschiebt die hochgeladene Datei aus dem temporären Verzeichnis in den endgültigen Pfad.
|
|
*
|
|
* @details Schlägt dieser Vorgang fehl, wird ein serverseitiges Error-Log geschrieben und eine Weiterleitung durchgeführt.
|
|
*/
|
|
if (!move_uploaded_file($tmp, $targetPath))
|
|
{
|
|
$lastErr = error_get_last();
|
|
$lastErrMsg = (is_array($lastErr) && isset($lastErr['message'])) ? (string)$lastErr['message'] : 'unknown';
|
|
error_log('Upload: move_uploaded_file failed to ' . $targetPath . ' - ' . $lastErrMsg);
|
|
header('Location: account.php?upload=err&code=move');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @var string $publicPath Öffentlich zugänglicher URL-Pfad relativ zum Webroot.
|
|
* Dies ist der Pfad, der in der Datenbank gespeichert wird.
|
|
*/
|
|
// Pfad, der in HTML genutzt wird (URL relativ zur Webroot)
|
|
$publicPath = 'assets/images/profilePictures/' . $filename;
|
|
|
|
/**
|
|
* @var string $servername Adresse des Datenbankservers (veraltet in diesem Skript, aber vorhanden).
|
|
*/
|
|
$servername = "localhost";
|
|
|
|
/**
|
|
* @var int $port Portadresse der Datenbank (ebenfalls ungenutzt im direkten Setup hier).
|
|
*/
|
|
$port = 3306;
|
|
|
|
/**
|
|
* @var mysqli|bool $conn Die per db_connect (aus lib/db.php) aufgebaute Verbindung zur Datenbank.
|
|
*/
|
|
$conn = db_connect();
|
|
|
|
/**
|
|
* @var mysqli_stmt|false $stmt Das vorbereitete Statement zur Aktualisierung des Benutzer-Profilbilds.
|
|
*/
|
|
$stmt = mysqli_prepare($conn, "UPDATE users SET profilePicture = ? WHERE userID = ?");
|
|
|
|
/**
|
|
* @brief Bricht ab, wenn das Prepared Statement nicht erstellt werden konnte.
|
|
*/
|
|
if (!$stmt)
|
|
{
|
|
mysqli_close($conn);
|
|
header('Location: account.php?upload=err');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @brief Bindet Parameter, führt das Statement aus und schließt anschließend Verbindung und Statement.
|
|
*/
|
|
mysqli_stmt_bind_param($stmt, 'si', $publicPath, $userId);
|
|
$ok = mysqli_stmt_execute($stmt);
|
|
mysqli_stmt_close($stmt);
|
|
$conn->close();
|
|
|
|
/**
|
|
* @brief Leitet bei einem fehlgeschlagenen Datenbankupdate zur Error-Variante der account.php weiter.
|
|
*/
|
|
if (!$ok)
|
|
{
|
|
header('Location: account.php?upload=err');
|
|
exit();
|
|
}
|
|
|
|
/**
|
|
* @brief Der Upload war erfolgreich; Umleitung zur Account-Seite mit Erfolgsmeldung.
|
|
*/
|
|
header('Location: account.php?upload=ok');
|
|
exit();
|
|
|