+ $block): ?>
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
; ?>)
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
@@ -289,4 +321,4 @@ include_once 'php/controller/showArticle-controller.php';
-
+
\ No newline at end of file
diff --git a/includes/article-block-helper.php b/includes/article-block-helper.php
new file mode 100644
index 0000000..98e5646
--- /dev/null
+++ b/includes/article-block-helper.php
@@ -0,0 +1,69 @@
+ 'text'|'image', 'value' => string]
+ */
+function rebuildBlocksFromPost(array $postBlocks, array $fileBlocks, string $uploadDir): array {
+ $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+
+ $keys = array_keys($postBlocks);
+ if (isset($fileBlocks['name']) && is_array($fileBlocks['name'])) {
+ $keys = array_unique(array_merge($keys, array_keys($fileBlocks['name'])));
+ }
+ sort($keys, SORT_NUMERIC);
+
+ $blocks = [];
+
+ foreach ($keys as $key) {
+ $type = $postBlocks[$key]['type'] ?? null;
+
+ if ($type === 'text') {
+ $blocks[] = [
+ 'type' => 'text',
+ 'value' => $postBlocks[$key]['text'] ?? '',
+ ];
+ } elseif ($type === 'image') {
+ // Vorbelegung: bereits vorhandenes Server-Bild (falls Datei nicht ersetzt wird)
+ $value = $postBlocks[$key]['existing_image'] ?? '';
+
+ $hasUpload = isset($fileBlocks['error'][$key]['image'])
+ && $fileBlocks['error'][$key]['image'] === UPLOAD_ERR_OK;
+
+ if ($hasUpload) {
+ $tmpName = $fileBlocks['tmp_name'][$key]['image'];
+ $originalName = $fileBlocks['name'][$key]['image'];
+ $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
+ if (!in_array($extension, $allowedExtensions, true)) {
+ $extension = 'jpg';
+ }
+
+ $fileName = 'img_' . uniqid() . '.' . $extension;
+ $destination = $uploadDir . $fileName;
+
+ if (move_uploaded_file($tmpName, $destination)) {
+ $value = 'uploads/' . $fileName;
+ }
+ // Bei Fehler: alter Wert (falls vorhanden) bleibt erhalten, Block wird nicht verworfen
+ }
+
+ $blocks[] = [
+ 'type' => 'image',
+ 'value' => $value,
+ ];
+ }
+ // unbekannter/fehlender type -> Block wird ignoriert
+ }
+
+ return $blocks;
+}
\ No newline at end of file
diff --git a/js/editor.js b/js/editor.js
index e666f40..01ee67f 100644
--- a/js/editor.js
+++ b/js/editor.js
@@ -1,172 +1,133 @@
-console.log("Die JavaScript-Datei wurde erfolgreich geladen!");
+//console.log("editor.js wurde erfolgreich geladen!");
function initEditor() {
const form = document.getElementById("editor-form");
if (!form) {
- console.error("Skript abgebrochen: Formular nicht gefunden!");
+ console.error("editor.js abgebrochen: Formular nicht gefunden!");
return;
} else {
- console.log("Formular gefunden und Editor initialisiert:", form);
+ console.log("Formular gefunden und editor.js 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 = [];
+ // Fortlaufender Zähler für eindeutige Block-Indizes. Wird nie wiederverwendet
+ // (auch nicht nach dem Löschen eines Blocks), damit sich neue und übrig
+ // gebliebene Blöcke beim finalen Submit nie einen Namen teilen.
+ let blockIndex = container.querySelectorAll(".editor-block").length;
+
+ // Ohne JS sind Textblock-/Bild-Button im Pop-up immer sichtbar und ganz normale
+ // Submit-Buttons. Erst mit JS blenden wir das Pop-up standardmäßig aus und
+ // steuern die Sichtbarkeit über den Plus-Button.
+ popup.classList.add("hidden");
- // 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() {
+ // Klick auf "Textblock" / "Bild einfügen": lokal im DOM anlegen statt zum
+ // Server zu submitten (entlastet den Server, kein Page-Reload nötig).
+ popup.querySelectorAll('[name="editor_action"]').forEach(btn => {
+ btn.addEventListener("click", function (e) {
+ e.preventDefault();
const type = this.getAttribute("data-type");
addBlockElement(type, "");
popup.classList.add("hidden");
});
});
- // Erstellt ein visuelles HTML-Element im Editor
+ // Bereits vom Server gerenderte Blöcke (z.B. beim Bearbeiten eines bestehenden
+ // Artikels oder nach einem Validierungsfehler) ebenfalls mit JS-Verhalten ausstatten.
+ container.querySelectorAll(".editor-block").forEach(blockDiv => {
+ bindDeleteButton(blockDiv);
+ bindImageInput(blockDiv);
+ });
+
+ // Erstellt einen neuen Block inkl. echter, benannter Formularfelder
+ // (blocks[i][type], blocks[i][text] bzw. blocks[i][image]). Diese Felder werden
+ // beim finalen Submit ganz normal vom Browser als multipart/form-data verschickt –
+ // es ist kein manuelles Zusammenbauen von JSON beim Absenden mehr nötig.
function addBlockElement(type, value = "") {
+ const index = blockIndex++;
+
const blockDiv = document.createElement("div");
- blockDiv.classList.add("editor-block");
- blockDiv.setAttribute("data-type", type);
+ blockDiv.classList.add("editor-block", "article-editor-scope");
+ blockDiv.setAttribute("data-index", String(index));
- // 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);
+ const typeInput = document.createElement("input");
+ typeInput.type = "hidden";
+ typeInput.name = `blocks[${index}][type]`;
+ typeInput.value = type;
+ blockDiv.appendChild(typeInput);
if (type === "text") {
const textarea = document.createElement("textarea");
+ textarea.name = `blocks[${index}][text]`;
textarea.placeholder = "Schreibe deinen Textblock...";
textarea.value = value;
blockDiv.appendChild(textarea);
} else if (type === "image") {
const fileInput = document.createElement("input");
fileInput.type = "file";
+ fileInput.name = `blocks[${index}][image]`;
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);
}
+ const deleteBtn = document.createElement("button");
+ deleteBtn.type = "submit";
+ deleteBtn.name = "editor_action";
+ deleteBtn.value = `delete_block:${index}`;
+ deleteBtn.classList.add("delete-block-btn");
+ deleteBtn.innerHTML = "✕";
+ blockDiv.appendChild(deleteBtn);
+
container.appendChild(blockDiv);
+
+ bindDeleteButton(blockDiv);
+ bindImageInput(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 });
+ // Löschen-Button eines Blocks lokal abfangen: entfernt den Block nur aus dem DOM,
+ // statt das Formular zum Server zu senden.
+ function bindDeleteButton(blockDiv) {
+ const btn = blockDiv.querySelector(".delete-block-btn");
+ if (!btn) return;
+ btn.addEventListener("click", (e) => {
+ e.preventDefault();
+ blockDiv.remove();
});
+ }
- // das reguläre unsichtbare Content-Feld befüllen
- hiddenContentInput.value = JSON.stringify(blocks);
+ // Zeigt bei Auswahl einer Bilddatei sofort eine Vorschau an. Rein optisch –
+ // der eigentliche Datei-Upload läuft nativ über das
.
+ function bindImageInput(blockDiv) {
+ const fileInput = blockDiv.querySelector('input[type="file"]');
+ if (!fileInput) return;
- // 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);
+ let imgPreview = blockDiv.querySelector(".block-image-preview");
+ if (!imgPreview) {
+ imgPreview = document.createElement("img");
+ imgPreview.classList.add("block-image-preview");
+ imgPreview.style.maxWidth = "200px";
+ imgPreview.style.display = "none";
+ imgPreview.style.marginTop = "10px";
+ blockDiv.insertBefore(imgPreview, fileInput.nextSibling);
}
- 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);
- }
+ fileInput.addEventListener("change", function () {
+ if (this.files && this.files[0]) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ imgPreview.src = e.target.result;
+ imgPreview.style.display = "block";
+ };
+ reader.readAsDataURL(this.files[0]);
+ }
+ });
}
}
@@ -176,4 +137,4 @@ if (document.readyState === "loading") {
} else {
// Falls das DOM schon fertig geladen ist, führen wir es direkt aus
initEditor();
-}
+}
\ No newline at end of file
diff --git a/php/controller/createArticle-controller.php b/php/controller/createArticle-controller.php
index a48a98a..de3ed9d 100644
--- a/php/controller/createArticle-controller.php
+++ b/php/controller/createArticle-controller.php
@@ -5,134 +5,120 @@ if (session_status() === PHP_SESSION_NONE) {
require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php';
require_once '../validator/article-validator.php';
+require_once '../../includes/article-block-helper.php';
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
- if ($_SERVER["REQUEST_METHOD"] === "POST") {
- $_SESSION["old_title"] = $_POST["title"] ?? '';
- $_SESSION["old_content"] = $_POST["content"] ?? '';
- $_SESSION["old_category"] = $_POST["category"] ?? '';
- $_SESSION["old_tags"] = $_POST["tags"] ?? '';
- if(!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
- $_SESSION["message"] = "missing_parameters";
+if ($_SERVER["REQUEST_METHOD"] === "POST") {
+
+ $uploadDir = __DIR__ . '/../../uploads/';
+ if (!file_exists($uploadDir)) {
+ mkdir($uploadDir, 0755, true);
+ }
+
+ // Formularzustand (Titel/Tags/Kategorie/Blöcke) immer sichern, damit er nach einem
+ // Redirect (PRG-Pattern oder Validierungsfehler) wieder angezeigt werden kann.
+ $_SESSION["old_title"] = $_POST["title"] ?? '';
+ $_SESSION["old_category"] = $_POST["category"] ?? '';
+ $_SESSION["old_tags"] = $_POST["tags"] ?? '';
+
+ $blocks = rebuildBlocksFromPost($_POST['blocks'] ?? [], $_FILES['blocks'] ?? [], $uploadDir);
+ $_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+
+ // ---------------------------------------------------------------------
+ // Zwischenspeichern
+ // ---------------------------------------------------------------------
+ if (isset($_POST['editor_action']) && $_POST['editor_action'] !== '') {
+ $action = $_POST['editor_action'];
+
+ if ($action === 'add_text') {
+ $blocks[] = ['type' => 'text', 'value' => ''];
+ } elseif ($action === 'add_image') {
+ $blocks[] = ['type' => 'image', 'value' => ''];
+ } elseif (str_starts_with($action, 'delete_block:')) {
+ $deleteIndex = (int) substr($action, strlen('delete_block:'));
+ unset($blocks[$deleteIndex]);
+ $blocks = array_values($blocks);
+ }
+
+ $_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+ header("location: ../../index.php?pfad=createArticle");
+ exit();
+ }
+
+ // ---------------------------------------------------------------------
+ // Echtes Veröffentlichen
+ // ---------------------------------------------------------------------
+ if (!isset($_POST["title"]) || !isset($_POST["category"])) {
+ $_SESSION["message"] = "missing_parameters";
+ header("location: ../../index.php?pfad=createArticle");
+ exit();
+ } else {
+ $title = $_POST["title"];
+ $content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+ $author = $_SESSION["user_email"];
+ $category = $_POST["category"];
+ $tags = $_POST['tags'] ?? '';
+
+ // -------------------------------- Validierung der Daten: -------------------------
+ if (!articleTitleValidator($title)) {
+ $_SESSION["message"] = "invalid_title";
+ header("location: ../../index.php?pfad=createArticle");
+ exit();
+ }
+
+ if (!articleContentValidator($content)) {
+ $_SESSION["message"] = "invalid_content";
+ header("location: ../../index.php?pfad=createArticle");
+ exit();
+ }
+
+ if (!articleCategoryValidator($category)) {
+ $_SESSION["message"] = "invalid_category";
+ header("location: ../../index.php?pfad=createArticle");
+ exit();
+ }
+
+ if (!articleTagValidator($tags)) {
+ $_SESSION["message"] = "invalid_tags";
header("location: ../../index.php?pfad=createArticle");
exit();
} else {
- $title = $_POST["title"];
- $content = $_POST["content"];
- $author = $_SESSION["user_email"];
- $category = $_POST["category"];
- $tags = $_POST['tags'] ?? '';
-
- // -------------------------------- Validierung der Daten: -------------------------
- if (!articleTitleValidator($title)) {
- $_SESSION["message"] = "invalid_title";
- header("location: ../../index.php?pfad=createArticle");
- exit();
+ $cleanedTags = [];
+ $rawTags = explode(',', $tags);
+ foreach ($rawTags as $rawTag) {
+ // Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
+ $tag = trim($rawTag);
+ $cleanedTags[] = $tag;
}
+ // Duplikate entfernen:
+ $cleanedTags = array_unique($cleanedTags);
+ $cleanedTags = implode(',', $cleanedTags);
+ }
- if (!articleContentValidator($content)) {
- $_SESSION["message"] = "invalid_content";
- header("location: ../../index.php?pfad=createArticle");
- exit();
- }
+ // ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
+ try {
+ $articleManager = ArticleManager::getInstance();
+ // $content enthält bereits die finalen "uploads/..."-Pfade (kein Base64 mehr),
+ // da rebuildBlocksFromPost() Datei-Uploads sofort verarbeitet.
+ $articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
- if (!articleCategoryValidator($category)) {
- $_SESSION["message"] = "invalid_category";
- header("location: ../../index.php?pfad=createArticle");
- exit();
- }
+ // Formulardaten nach erfolgreichem Erstellen aus der Session löschen
+ unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
- if (!articleTagValidator($tags)) {
- $_SESSION["message"] = "invalid_tags";
- header("location: ../../index.php?pfad=createArticle");
- exit();
- } else {
- $cleanedTags = [];
- $rawTags = explode(',', $tags);
- foreach ($rawTags as $rawTag) {
- // Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
- $tag = trim($rawTag);
- $cleanedTags[] = $tag;
- }
- // Duplikate entfernen:
- $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();
- $articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
-
- // Formulardaten nach erfolgreichem Erstellen aus der Session löschen
- unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
-
- } catch (\Throwable $e){
- $_SESSION["message"] = "internal_error";
- header("location: ../../index.php?pfad=createArticle");
- exit();
- }
-
- $_SESSION["message"] = "new_article";
- // Weiterleitung zur Homepage
- header("location: ../../index.php");
+ } catch (\Throwable $e) {
+ $_SESSION["message"] = "internal_error";
+ header("location: ../../index.php?pfad=createArticle");
exit();
}
+
+ $_SESSION["message"] = "new_article";
+ // Weiterleitung zur Homepage
+ header("location: ../../index.php");
+ exit();
}
+}
?>
\ No newline at end of file
diff --git a/php/controller/updateArticle-controller.php b/php/controller/updateArticle-controller.php
index c5ef8f6..cb58617 100644
--- a/php/controller/updateArticle-controller.php
+++ b/php/controller/updateArticle-controller.php
@@ -7,17 +7,14 @@ require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php';
require_once '../model/Article.php';
require_once '../validator/article-validator.php';
+require_once '../../includes/article-block-helper.php';
-if (!isset($_SESSION["user_email"])) {
+if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
- $_SESSION["old_title"] = $_POST["title"] ?? '';
- $_SESSION["old_content"] = $_POST["content"] ?? '';
- $_SESSION["old_category"] = $_POST["category"] ?? '';
- $_SESSION["old_tags"] = $_POST["tags"] ?? '';
if (isset($_GET["id"]) && !empty($_GET["id"])) {
$id = $_GET["id"];
@@ -41,16 +38,56 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
exit();
}
- if (!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
+ $uploadDir = __DIR__ . '/../../uploads/';
+ if (!file_exists($uploadDir)) {
+ mkdir($uploadDir, 0755, true);
+ }
+
+ // Formularzustand (Titel/Tags/Kategorie/Blöcke) immer sichern, damit er nach einem
+ // Redirect (PRG-Pattern oder Validierungsfehler) wieder angezeigt werden kann.
+ $_SESSION["old_title"] = $_POST["title"] ?? '';
+ $_SESSION["old_tags"] = $_POST["tags"] ?? '';
+ $_SESSION["old_category"] = $_POST["category"] ?? '';
+
+ $blocks = rebuildBlocksFromPost($_POST['blocks'] ?? [], $_FILES['blocks'] ?? [], $uploadDir);
+ $_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+
+ // ---------------------------------------------------------------------
+ // Zwischen-Schritt: Block hinzufügen oder entfernen (kein echtes Speichern).
+ // Wird bei aktivem JavaScript per preventDefault() abgefangen und lokal im
+ // DOM erledigt (js/editor.js) – ohne JS läuft dieser Server-Roundtrip.
+ // ---------------------------------------------------------------------
+ if (isset($_POST['editor_action']) && $_POST['editor_action'] !== '') {
+ $action = $_POST['editor_action'];
+
+ if ($action === 'add_text') {
+ $blocks[] = ['type' => 'text', 'value' => ''];
+ } elseif ($action === 'add_image') {
+ $blocks[] = ['type' => 'image', 'value' => ''];
+ } elseif (str_starts_with($action, 'delete_block:')) {
+ $deleteIndex = (int) substr($action, strlen('delete_block:'));
+ unset($blocks[$deleteIndex]);
+ $blocks = array_values($blocks);
+ }
+
+ $_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+ header("location: ../../index.php?pfad=updateArticle&id=$id");
+ exit();
+ }
+
+ // ---------------------------------------------------------------------
+ // Echtes Speichern
+ // ---------------------------------------------------------------------
+ if (!isset($_POST["title"]) || !isset($_POST["category"])) {
$_SESSION["message"] = "missing_parameters";
header("location: ../../index.php?pfad=updateArticle&id=$id");
exit();
- }else{
- $title = $_POST["title"];
- $content = $_POST["content"];
- $author = $_SESSION["user_email"];
+ } else {
+ $title = $_POST["title"];
+ $content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
+ $author = $_SESSION["user_email"];
$category = $_POST["category"];
- $tags = $_POST['tags'] ?? '';
+ $tags = $_POST['tags'] ?? '';
// -------------------------------- Validierung der Daten: -------------------------
if (!articleTitleValidator($title)) {
@@ -88,91 +125,56 @@ 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);
- }
+ // ----------------- Verwaiste Bilder aufräumen -----------------
+ // Bilder, die im alten (gespeicherten) Content vorkamen, im neuen aber nicht
+ // mehr referenziert werden, wurden vom Nutzer entfernt oder ersetzt -> löschen.
+ // Hinweis/TODO: Bilder, die innerhalb derselben Bearbeitungs-Sitzung neu
+ // hochgeladen und noch vor dem finalen Speichern wieder entfernt wurden,
+ // werden hierüber nicht erfasst (sie tauchten nie im alten Content auf) und
+ // bleiben als Datei liegen. Für eine vollständige Bereinigung würde sich ein
+ // regelmäßiger Cleanup-Job anbieten, der verwaiste Dateien im uploads/-Ordner
+ // mit den in der Datenbank referenzierten Pfaden abgleicht.
+ $oldBlocks = json_decode($article->getContent(), true);
+ $oldImagePaths = [];
+ if (is_array($oldBlocks)) {
+ foreach ($oldBlocks as $oldBlock) {
+ if (($oldBlock['type'] ?? '') === 'image'
+ && !empty($oldBlock['value'])
+ && is_string($oldBlock['value'])
+ && str_starts_with($oldBlock['value'], 'uploads/')) {
+ $oldImagePaths[] = $oldBlock['value'];
}
}
- }*/
-
- // ----------------------- 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);
+ $newImagePaths = [];
+ foreach ($blocks as $block) {
+ if (($block['type'] ?? '') === 'image' && !empty($block['value'])) {
+ $newImagePaths[] = $block['value'];
+ }
+ }
+
+ $orphanedImages = array_diff($oldImagePaths, $newImagePaths);
+ foreach ($orphanedImages as $orphanedImage) {
+ $absolutePath = __DIR__ . '/../../' . $orphanedImage;
+ if (is_file($absolutePath)) {
+ @unlink($absolutePath);
+ }
+ }
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
try {
$articleManager = ArticleManager::getInstance();
$article = $articleManager->getArticle($id);
$article->setTitle($title);
- $article->setContent($finalContent);
+ $article->setContent($content);
$article->setCategory($category);
$article->setTags($cleanedTags);
- $articleManager->updateArticle($id ,$article, $author);
+ $articleManager->updateArticle($id, $article, $author);
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
- } catch (\Throwable $e){
+ } catch (\Throwable $e) {
$_SESSION["message"] = $e->getMessage();
header("location: ../../index.php?pfad=updateArticle&id=$id");
exit();
diff --git a/php/model/DatabaseInitializer.php b/php/model/DatabaseInitializer.php
index f928be1..5e09de9 100644
--- a/php/model/DatabaseInitializer.php
+++ b/php/model/DatabaseInitializer.php
@@ -234,13 +234,18 @@ class DatabaseInitializer {
// Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails
$randomAuthor = $availableEmails[array_rand($availableEmails)];
+ // Text in (blocks[i][type]/[text]/[image])-Format umwandeln:
+ $content = json_encode([
+ ['type' => 'text', 'value' => $article[1]]
+ ], JSON_UNESCAPED_UNICODE);
+
$articleInsertStmt->execute([
':title' => $article[0],
- ':content' => $article[1],
+ ':content' => $content,
':author' => $randomAuthor,
':category' => $article[2],
':tags' => $article[3]
]);
}
}
-}
+}
\ No newline at end of file