diff --git a/README.md b/README.md index 419774f..8cb2ec7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Login-Informationen für Dummy-User `Anmeldename, Passwort, Mailadresse`: -- `max.mustermann, test12345, mustermann@web.de` +- `max.mustermann, test12345, max.mustermann@web.de` ## Weitere Voraussetzungen zur Nutzung - Per Klick auf das Logo gelangt man auf die Home-Seite. @@ -18,6 +18,7 @@ - Bitte auf die gesetzten TODO's achten. Wenn Inhalte fehlen, sind sie i.d.R. als TODO kommentiert. - Die Suchseite und Kategorieseite packen momentan alle passenden Beiträge untereinander. Später sollen zunächst 10 Ergebnisse auf einer Seite angezeigt werden. +- Wenn ein Bild aus einem Beitrag entfernt wird, dann wird noch nicht die Datei im Pfad /uploads gelöscht. ## Besonderheiten des Projektes - Es wurde ein einfacher Beitrags-Editor erstellt. Mit diesem können Beiträge erstellt oder bearbeitet werden. diff --git a/content/createArticle.php b/content/createArticle.php index 072dfdb..959583e 100644 --- a/content/createArticle.php +++ b/content/createArticle.php @@ -17,8 +17,28 @@ if (!isset($_SESSION["user"])) { - + +
+ + +
+ + +
+ + + diff --git a/content/showArticle.php b/content/showArticle.php index fc8502e..d461d16 100644 --- a/content/showArticle.php +++ b/content/showArticle.php @@ -29,39 +29,12 @@ if (isset($_GET["id"])) { Funktion: Stellt einen übergebenen Beitrag dar. --> -
- -

- Es ist ein interner Fehler aufgetreten. Bitte versuche es erneut. -

- - - -

- Es ist ein Fehler aufgetreten. Die ID konnte nicht ausgelesen werden. Bitte versuche es erneut. -

- - - -

- Jeder Beitrag muss einen Titel, Kategorie und Inhalt besitzen. -

- - -

- Dein Beitrag wurde erfolgreich bearbeitet! -

- - - - + -
+ @@ -72,44 +45,60 @@ if (isset($_GET["id"])) {
- - Von: - + Von:
+
-
- -
- -
+ + +
+ +
+ + +
+ Beitragsbild +
+ + +
+ +
+
+ - -
Tags:
-
- - - - +
diff --git a/content/updateArticle.php b/content/updateArticle.php index 69a726d..8555072 100644 --- a/content/updateArticle.php +++ b/content/updateArticle.php @@ -10,7 +10,7 @@ include_once 'php/controller/showArticle-controller.php'; Seite: Beitrag erstellen Inhalt: Formular für die Erstellung eines neuen Beitrags --> -
" id="editor-form" class="article-editor-scope.editor-container article-editor-scope editor-container"> +" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
@@ -26,8 +26,33 @@ include_once 'php/controller/showArticle-controller.php'; ?>" placeholder="Titel hier eingeben" required> - + +
+ + +
+ + +
+ + + + + +
diff --git a/css/createArticle.css b/css/createArticle.css index ced5d7e..51555c7 100644 --- a/css/createArticle.css +++ b/css/createArticle.css @@ -90,6 +90,135 @@ padding: 18px 12px; } +/* Container für die dynamisch per JS eingefügten Blöcke */ +.article-editor-scope #block-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 20px; +} + +/* Styling für jeden einzelnen dynamisch generierten Block */ +.article-editor-scope .editor-block { + position: relative; /* Wichtig für die absolute Positionierung des Lösch-Buttons */ + width: 100%; + padding: 15px; + background-color: #fafafa; + border: 1px dashed #cccccc; + border-radius: 6px; +} + +/* Textarea innerhalb eines dynamischen Textblocks */ +.article-editor-scope .editor-block textarea { + width: 100%; + min-height: 120px; + padding: 10px; + border: 1px solid #dddddd; + border-radius: 4px; + font-family: inherit; + font-size: 1.1rem; + line-height: 1.6; + resize: vertical; + outline: none; + background: #ffffff; +} + +/* Komfort-Löschbutton oben rechts an jedem Block */ +.article-editor-scope .delete-block-btn { + position: absolute; + top: -10px; + right: -10px; + width: 24px; + height: 24px; + border-radius: 50%; + background-color: #e74c3c; + color: #ffffff; + border: none; + font-size: 12px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + transition: background-color 0.2s ease; +} + +.article-editor-scope .delete-block-btn:hover { + background-color: #c0392b; +} + +/* Steuerungselement für den Plus-Button und das Pop-up */ +.article-editor-scope .add-block-control { + position: relative; /* Dient als Anker für das absolut positionierte Pop-up */ + margin-top: 10px; + display: inline-block; + align-self: flex-start; /* Verhindert, dass der Button die volle Breite spannt */ +} + +/* Der runde Plus-Button */ +.article-editor-scope .plus-button { + width: 45px; + height: 45px; + border-radius: 50%; + background-color: #3498db; + color: #ffffff; + border: none; + font-size: 26px; + font-weight: 300; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + transition: background-color 0.2s ease, transform 0.2s ease; +} + +.article-editor-scope .plus-button:hover { + background-color: #2980b9; + transform: scale(1.05); +} + +/* Das Pop-up Menü */ +.article-editor-scope .block-popup { + position: absolute; + left: 60px; /* Platziert das Menü rechts neben dem Plus-Button */ + top: 50%; + transform: translateY(-50%); /* Zentriert das Menü vertikal zum Button */ + background-color: #ffffff; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 8px; + display: flex; + gap: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + z-index: 999; + white-space: nowrap; +} + +/* Die entscheidende Klasse zum Ausblenden */ +.article-editor-scope .block-popup.hidden { + display: none !important; +} + +/* Buttons im Pop-up (Textblock / Bild einfügen) */ +.article-editor-scope .block-popup button { + background-color: #f8f9fa; + border: 1px solid #dcdde1; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + color: #2f3640; + transition: background-color 0.15s ease, border-color 0.15s ease; +} + +.article-editor-scope .block-popup button:hover { + background-color: #f1f2f6; + border-color: #b2bec3; +} + /* Responsive Anpassungen unter 760px (für z.B. Smartphones) */ @media (max-width: 760px) { .article-editor-scope.editor-container { @@ -113,4 +242,8 @@ border-left: none; border-top: 1px solid #e0e0e0; } + + .article-editor-scope .editor-block textarea { + min-height: 150px; + } } \ No newline at end of file diff --git a/css/showArticle.css b/css/showArticle.css index 60eef65..46ffbad 100644 --- a/css/showArticle.css +++ b/css/showArticle.css @@ -105,6 +105,37 @@ cursor: default; } +.article-view-content { + margin-bottom: 3rem; + display: flex; + flex-direction: column; + gap: 1.5rem; /* Erzeugt einen sauberen Abstand zwischen den einzelnen Blöcken */ + width: 100%; +} + +.article-view-body { + font-size: 1.125rem; + color: #2d3748; + width: 100%; +} + +.article-view-body.block-text { + white-space: pre-line; + word-break: break-word; +} + +.article-view-body.block-image { + display: flex; + justify-content: center; /* Zentriert das Bild horizontal */ +} + +.article-view-body.block-image img { + max-width: 100%; /* Verhindert das Ausbrechen aus der Lesebreite */ + height: auto; /* Behält das originale Seitenverhältnis bei */ + border-radius: 6px; /* Optionale leichte Rundung für ein moderneres Layout */ + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03); /* Minimaler, eleganter Schatten */ +} + /* Responsive Anpassungen unter 760px (für z.B. Smarticlephones) */ @media (max-width: 760px) { .article-view-container { diff --git a/includes/alertMessages.php b/includes/alertMessages.php index f88f3ae..87cdbfd 100644 --- a/includes/alertMessages.php +++ b/includes/alertMessages.php @@ -15,7 +15,7 @@

- Der Text erlaubt eine Länge von 10 bis maximal 7.000 Zeichen (ca. 1.000 Wörter). + Ein Beitrag muss Inhalt besitzen. Text- und Bildelemente dürfen nicht leer sein!

@@ -63,6 +63,11 @@ Dein Beitrag wurde erfolgreich veröffentlicht!

+ +

+ Das Bild konnte nicht hochgeladen werden. Bitte versuche es erneut oder verwende ein anderes Bildformat. +

+ diff --git a/index.php b/index.php index c61be2c..0db18ae 100644 --- a/index.php +++ b/index.php @@ -54,6 +54,7 @@ if ($pfad === "deleteAccount") { + EduForge diff --git a/js/editor.js b/js/editor.js new file mode 100644 index 0000000..e666f40 --- /dev/null +++ b/js/editor.js @@ -0,0 +1,179 @@ +console.log("Die JavaScript-Datei wurde erfolgreich geladen!"); + +function initEditor() { + const form = document.getElementById("editor-form"); + + if (!form) { + console.error("Skript abgebrochen: Formular nicht gefunden!"); + return; + } else { + console.log("Formular gefunden und Editor initialisiert:", form); + } + + const container = document.getElementById("block-container"); + const plusButton = document.getElementById("plus-button"); + const popup = document.getElementById("block-popup"); + const hiddenContentInput = document.getElementById("content"); + + const initialImages = []; + + // Pop-up umschalten bei Klick auf das Plus + plusButton.addEventListener("click", () => { + popup.classList.toggle("hidden"); + }); + + // Klick auf eine Block-Option im Pop-up + popup.querySelectorAll("button").forEach(btn => { + btn.addEventListener("click", function() { + const type = this.getAttribute("data-type"); + addBlockElement(type, ""); + popup.classList.add("hidden"); + }); + }); + + // Erstellt ein visuelles HTML-Element im Editor + function addBlockElement(type, value = "") { + const blockDiv = document.createElement("div"); + blockDiv.classList.add("editor-block"); + blockDiv.setAttribute("data-type", type); + + // Wenn es ein existierendes Server-Bild beim Laden ist, Pfad im globalen Array sichern + if (type === "image" && value && typeof value === 'string' && value.startsWith('uploads/')) { + initialImages.push(value); + blockDiv.setAttribute("data-value", value); + } + + // Löschen-Button + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.innerHTML = "✕"; + deleteBtn.classList.add("delete-block-btn"); + deleteBtn.addEventListener("click", () => { + // ANPASSUNG 2B: Logik hier komplett geleert. Das '✕' entfernt den Block jetzt nur noch sicher aus dem HTML. + blockDiv.remove(); + }); + blockDiv.appendChild(deleteBtn); + + if (type === "text") { + const textarea = document.createElement("textarea"); + textarea.placeholder = "Schreibe deinen Textblock..."; + textarea.value = value; + blockDiv.appendChild(textarea); + } else if (type === "image") { + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + + const imgPreview = document.createElement("img"); + imgPreview.style.maxWidth = "200px"; + imgPreview.style.display = "block"; + imgPreview.style.marginTop = "10px"; + + if (value && typeof value === 'string') { + if (value.startsWith('uploads/') || value.startsWith('data:image/')) { + imgPreview.src = value; + blockDiv.setAttribute("data-value", value); + } + } + + fileInput.addEventListener("change", function() { + if (this.files && this.files[0]) { + const reader = new FileReader(); + reader.onload = function(e) { + imgPreview.src = e.target.result; + blockDiv.setAttribute("data-value", e.target.result); + } + reader.readAsDataURL(this.files[0]); + } + }); + + blockDiv.appendChild(fileInput); + blockDiv.appendChild(imgPreview); + } + + container.appendChild(blockDiv); + } + + // beim Abschicken verbleibende Blöcke auslesen UND gelöschte Bilder ermitteln + form.addEventListener("submit", function(e) { + const blocks = []; + const currentImages = []; + + // alle aktuell im Formular verbliebenen Blöcke scannen + container.querySelectorAll(".editor-block").forEach(blockDiv => { + const type = blockDiv.getAttribute("data-type"); + let value = ""; + + if (type === "text") { + value = blockDiv.querySelector("textarea").value; + } else if (type === "image") { + + const imgTag = blockDiv.querySelector("img"); + if (imgTag) { + const srcValue = imgTag.getAttribute("src") || ""; + // Wenn es ein neues Bild ist, nutzen wir das data-value (Base64) + if (srcValue.startsWith('data:image/')) { + value = blockDiv.getAttribute("data-value") || ""; + } else { + value = srcValue; + } + } + + // Pfade sammeln, die der Nutzer NICHT gelöscht hat (für den Abgleich) + if (value && value.startsWith('uploads/')) { + currentImages.push(value); + } + } + + blocks.push({ type: type, value: value }); + }); + + // das reguläre unsichtbare Content-Feld befüllen + hiddenContentInput.value = JSON.stringify(blocks); + + // Differenz berechnen: Welche Bilder aus 'initialImages' fehlen in 'currentImages' ? + const deletedImages = initialImages.filter(img => !currentImages.includes(img)); + + // das 'deleted_images'-Feld dynamisch befüllen und an den Controller senden + let deletedInput = document.getElementById("deleted-images"); + if (!deletedInput) { + deletedInput = document.createElement("input"); + deletedInput.type = "hidden"; + deletedInput.id = "deleted-images"; + deletedInput.name = "deleted_images"; + form.appendChild(deletedInput); + } + deletedInput.value = JSON.stringify(deletedImages); + }); + + // Existierende Blöcke laden (stellt alte Daten aus der Session wieder her) + try { + const initialBlocks = JSON.parse(hiddenContentInput.value.trim()); + if (Array.isArray(initialBlocks)) { + initialBlocks.forEach(b => { + if (b.type === "image" && b.value && typeof b.value === 'string' && !b.value.startsWith('data:image/')) { + let cleanPath = b.value.trim().replace(/\\\//g, '/'); // Verwandelt \/ in / + + initialImages.push(cleanPath); + addBlockElement(b.type, cleanPath); + + } else { + addBlockElement(b.type, b.value); + } + }); + console.log("Erfolgreich registrierte Start-Bilder:", initialImages); + } + } catch(e) { + if (hiddenContentInput.value.trim() !== "") { + addBlockElement("text", hiddenContentInput.value); + } + } +} + +// SICHERER START: Prüft, ob das HTML bereits bereit ist +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initEditor); +} else { + // Falls das DOM schon fertig geladen ist, führen wir es direkt aus + initEditor(); +} diff --git a/php/controller/createArticle-controller.php b/php/controller/createArticle-controller.php index 53c7f3a..2022393 100644 --- a/php/controller/createArticle-controller.php +++ b/php/controller/createArticle-controller.php @@ -24,12 +24,6 @@ require_once '../validator/article-validator.php'; $tags = $_POST['tags'] ?? ''; // -------------------------------- Validierung der Daten: ------------------------- - if (!articleAuthorValidator($author)) { - $_SESSION["message"] = "author_not_valid"; - header("location: ../../index.php?pfad=createArticle"); - exit(); - } - if (!articleTitleValidator($title)) { $_SESSION["message"] = "invalid_title"; header("location: ../../index.php?pfad=createArticle"); @@ -64,6 +58,59 @@ require_once '../validator/article-validator.php'; $cleanedTags = array_unique($cleanedTags); $cleanedTags = implode(',', $cleanedTags); } + + // ----------------- Base64-Bilder verarbeiten und auf Server speichern ----------------- + $blocks = json_decode($content, true); + $uploadDir = __DIR__ . '/../../uploads/'; + + if (!file_exists($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + if (is_array($blocks)) { + foreach ($blocks as &$block) { + // sicherstellen, dass 'type' und 'value' existieren: + if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && str_starts_with($block['value'], 'data:image/')) { + + // Base64-String zerlegen + $parts = explode(',', $block['value']); + + // falls der String korrupt ist und kein Komma hat + if (count($parts) < 2) { + continue; + } + + $metadata = $parts[0]; + $base64Data = $parts[1]; + + // Dateiendung ermitteln + preg_match('/data:image\/(?.*?);/', $metadata, $matches); + $extension = $matches['extension'] ?? 'jpg'; + if ($extension === 'jpeg') { + $extension = 'jpg'; + } + + // Eindeutigen Dateinamen generieren + $fileName = 'img_' . uniqid() . '.' . $extension; + $filePath = $uploadDir . $fileName; + + // Datei im /uploads speichern: + if (file_put_contents($filePath, base64_decode($base64Data)) !== false) { + // temporären Base64-String durch den echten Pfad ersetzen + $block['value'] = 'uploads/' . $fileName; + } else { + $_SESSION["message"] = "image_upload_error"; + header("location: ../../index.php?pfad=createArticle"); + exit(); + } + } + } + unset($block); + } + + // Aktualisiertes Array wieder in JSON konvertieren + $finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE); + // ----------------- Übertragung der validierten Daten in ArticleManager: --------------------------- try { $articleManager = ArticleManager::getInstance(); @@ -72,7 +119,7 @@ require_once '../validator/article-validator.php'; // Formulardaten nach erfolgreichem Erstellen aus der Session löschen unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]); - } catch (Exception $e){ + } catch (\Throwable $e){ $_SESSION["message"] = "internal_error"; header("location: ../../index.php?pfad=createArticle"); exit(); @@ -84,7 +131,4 @@ require_once '../validator/article-validator.php'; exit(); } } - - - ?> \ No newline at end of file diff --git a/php/controller/updateArticle-controller.php b/php/controller/updateArticle-controller.php index cc881fd..6a863d1 100644 --- a/php/controller/updateArticle-controller.php +++ b/php/controller/updateArticle-controller.php @@ -34,12 +34,6 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") { $tags = $_POST['tags'] ?? ''; // -------------------------------- Validierung der Daten: ------------------------- - if (!articleAuthorValidator($author)) { - $_SESSION["message"] = "author_not_valid"; - header("location: ../../index.php?pfad=updateArticle&id=$id"); - exit(); - } - if (!articleTitleValidator($title)) { $_SESSION["message"] = "invalid_title"; header("location: ../../index.php?pfad=updateArticle&id=$id"); @@ -75,16 +69,91 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") { $cleanedTags = implode(',', $cleanedTags); } + // --------------------------------------- Base64-Bilder speichern --------------------------------------------- + $blocks = json_decode($content, true); + $uploadDir = __DIR__ . '/../../uploads/'; + + if (!file_exists($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + // ----------------- Gelöschte Bilder über die JS-Löschliste entfernen ----------------- TODO: Gelöschte Bilder über die JS-Löschliste entfernen + /*if (isset($_POST['deleted_images'])) { + $deletedImages = json_decode($_POST['deleted_images'], true); + + // Wir ermitteln den physisch echten, absoluten Pfad zum uploads-Ordner auf der Festplatte + $uploadDir = realpath(__DIR__ . '/../../uploads') . DIRECTORY_SEPARATOR; + + if (is_array($deletedImages)) { + foreach ($deletedImages as $imagePath) { + // Nur den reinen Dateinamen heraustrennen (z.B. img_65a123.jpg) + $filename = basename($imagePath); + $fullDeletePath = $uploadDir . $filename; + + // Debugging & Löschen: + if (file_exists($fullDeletePath)) { + // Versuchen zu löschen. Wenn es fehlschlägt, Fehlermeldung erzwingen + if (!@unlink($fullDeletePath)) { + $error = error_get_last(); + die("Datei existiert, aber PHP darf sie nicht löschen! Grund: " . $error['message']); + } + } else { + // Wenn PHP die Datei an diesem Pfad nicht findet, brechen wir zum Debuggen ab + // die("PHP findet die Datei nicht unter dem Pfad: " . $fullDeletePath); + } + } + } + }*/ + + // ----------------------- NEU hinzugefügte Base64-Bilder: -------------------------- + if (is_array($blocks)) { + foreach ($blocks as &$block) { + // Prüfen, ob der Block ein Bild ist und ein NEUES Bild (Base64-Format) enthält + if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && is_string($block['value'])) { + + if (str_starts_with($block['value'], 'data:image/')) { + $parts = explode(',', $block['value']); + if (count($parts) >= 2) { + $metadata = $parts[0]; + $base64Data = $parts[1]; + + preg_match('/data:image\/(?.*?);/', $metadata, $matches); + $extension = $matches['extension'] ?? 'jpg'; + if ($extension === 'jpeg') { $extension = 'jpg'; } + + $fileName = 'img_' . uniqid() . '.' . $extension; + $filePath = $uploadDir . $fileName; + + if (file_put_contents($filePath, base64_decode($base64Data)) !== false) { + $block['value'] = 'uploads/' . $fileName; + } else { + $_SESSION["message"] = "image_upload_error"; + header("location: ../../index.php?pfad=updateArticle&id=$id"); + exit(); + } + } + } + } + } + unset($block); + } + + // Aktualisiertes Array wieder in JSON konvertieren + $finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE); + // ----------------- Übertragung der validierten Daten in ArticleManager: --------------------------- try { $articleManager = ArticleManager::getInstance(); $article = $articleManager->getArticle($id); $article->setTitle($title); - $article->setContent($content); + $article->setContent($finalContent); $article->setCategory($category); $article->setTags($cleanedTags); $articleManager->updateArticle($id ,$article, $author); - } catch (Exception $e){ + + unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]); + + } catch (\Throwable $e){ $_SESSION["message"] = $e->getMessage(); header("location: ../../index.php?pfad=updateArticle&id=$id"); exit(); diff --git a/php/model/ArticleManager.php b/php/model/ArticleManager.php index 435ac90..52d8bc3 100644 --- a/php/model/ArticleManager.php +++ b/php/model/ArticleManager.php @@ -145,9 +145,17 @@ class ArticleManager // Verteilt die 10 Autoren gleichmäßig (ID 1 -> Autor 1, ID 10 -> Autor 10, ID 11 -> Autor 1) $authorEmail = $authors[($id - 1) % 10]; + $blockStructure = [ + [ + 'type' => 'text', + 'value' => $data[1] // Der originale Text aus dem Dummy-Array + ] + ]; + $jsonContent = json_encode($blockStructure, JSON_UNESCAPED_UNICODE); + $articleManager->addArticle( $data[0], // Titel - $data[1], // Inhalt + $jsonContent, // Inhalt $authorEmail, // Rotierende Autoren-E-Mail $data[2], // Kategorie $data[3] // Tags diff --git a/php/validator/article-validator.php b/php/validator/article-validator.php index d708258..e07b810 100644 --- a/php/validator/article-validator.php +++ b/php/validator/article-validator.php @@ -1,16 +1,5 @@ = 10) { - return true; - }else{ + // 1. Grundlegende Typprüfung + if (!is_string($content)) { return false; } + + // 2. Formale JSON-Prüfung (Kompatibel mit PHP 8.2) + $blocks = json_decode($content, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return false; + } + + // 3. Inhaltliche Validierung der einzelnen Blöcke + // Falls das JSON zwar valide, aber kein Array ist (z.B. nur ein String/Zahl) + if (!is_array($blocks)) { + return false; + } + + // Mindestens ein Block sollte vorhanden sein (optional, verhindert leere Beiträge) + if (empty($blocks)) { + return false; + } + + foreach ($blocks as $block) { + // Jeder Block muss die Keys 'type' und 'value' besitzen + if (!isset($block['type']) || !isset($block['value'])) { + return false; + } + + $type = $block['type']; + $value = $block['value']; + + if ($type === 'text') { + // Validierung für Text: Darf nach dem Trimmen nicht leer sein + if (trim($value) === '') { + return false; + } + } elseif ($type === 'image') { + // Validierung für Bild: Muss entweder mit uploads/ starten (Bestand) + // oder mit data:image/ beginnen (neues Base64-Bild aus dem Editor) + if (!is_string($value)) { + return false; + } + + $isValidPath = str_starts_with($value, 'uploads/'); + $isValidBase64 = str_starts_with($value, 'data:image/'); + + if (!$isValidPath && !$isValidBase64) { + return false; + } + } else { + // Unbekannter Blocktyp wird zur Sicherheit abgewiesen + return false; + } + } + + // Wenn alle Prüfungen bestanden wurden + return true; } /** diff --git a/uploads/.htaccess b/uploads/.htaccess new file mode 100644 index 0000000..b2891c3 --- /dev/null +++ b/uploads/.htaccess @@ -0,0 +1,14 @@ +# Verhindert das Auflisten aller Dateien im Ordner +Options -Indexes + +# Schaltet die PHP-Ausführung in diesem Ordner komplett ab + + Order Deny,Allow + Deny from all + + +# erlaubt nur den Zugriff auf folgende Dateien: + + Order Allow,Deny + Allow from all + \ No newline at end of file