From 80f92a384e92bb2a379dcb06c6a5290fc57294e3 Mon Sep 17 00:00:00 2001 From: NOrtmann1 Date: Sun, 14 Jun 2026 10:44:17 +0200 Subject: [PATCH 01/74] erweiterter Beitragseditor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit content ist nun im json-Format Bilder können hochgeladen werden Textblöcke können im Editor angehangen werden --- content/createArticle.php | 23 +++- includes/alertMessages.php | 5 + index.php | 3 + js/editor.js | 119 ++++++++++++++++++++ php/controller/createArticle-controller.php | 49 +++++++- php/model/ArticleManager.php | 4 +- php/validator/article-validator.php | 13 +-- 7 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 js/editor.js diff --git a/content/createArticle.php b/content/createArticle.php index 7dd9274..6554348 100644 --- a/content/createArticle.php +++ b/content/createArticle.php @@ -17,8 +17,29 @@ if (!isset($_SESSION["user"])) { - + +
+ + +
+ + +
+ + + diff --git a/includes/alertMessages.php b/includes/alertMessages.php index f88f3ae..72d63d6 100644 --- a/includes/alertMessages.php +++ b/includes/alertMessages.php @@ -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 175830a..5df0b58 100644 --- a/index.php +++ b/index.php @@ -42,6 +42,7 @@ if ($pfad === "deleteAccount") { + @@ -50,6 +51,8 @@ if ($pfad === "deleteAccount") { + + EduForge diff --git a/js/editor.js b/js/editor.js new file mode 100644 index 0000000..b22027c --- /dev/null +++ b/js/editor.js @@ -0,0 +1,119 @@ +/** + * Editor-Steuerung für dynamische Inhaltsblöcke + */ +document.addEventListener("DOMContentLoaded", function() { + const form = document.getElementById("editor-form"); + + // Falls das Formular auf der aktuellen Seite nicht existiert, Skript abbrechen + if (!form) { + return; + } + + const container = document.getElementById("block-container"); + const plusButton = document.getElementById("plus-button"); + const popup = document.getElementById("block-popup"); + const hiddenContentInput = document.getElementById("content"); + + // 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); + + // Löschen-Button für den Block + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.innerHTML = "✕"; + deleteBtn.classList.add("delete-block-btn"); + deleteBtn.addEventListener("click", () => { + 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"; + + // Falls bereits ein Bildpfad existiert (z.B. nach einem Reload bei Validierungsfehlern) + if (value && typeof value === 'string' && value.startsWith('uploads/')) { + 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; + // Temporäre Speicherung des Base64-Strings im HTML-Attribut + blockDiv.setAttribute("data-value", e.target.result); + } + reader.readAsDataURL(this.files[0]); + } + }); + + blockDiv.appendChild(fileInput); + blockDiv.appendChild(imgPreview); + } + + container.appendChild(blockDiv); + } + + // Beim Abschicken: HTML-Blöcke auslesen und als JSON-String serialisieren + form.addEventListener("submit", function(e) { + const blocks = []; + 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") { + value = blockDiv.getAttribute("data-value") || ""; + } + + blocks.push({ type: type, value: value }); + }); + + // JSON-Daten in das unsichtbare Formularfeld schreiben + hiddenContentInput.value = JSON.stringify(blocks); + }); + + // Existierende Blöcke laden (stellt alte Daten aus der Session wieder her) + try { + const initialBlocks = JSON.parse(hiddenContentInput.value); + if (Array.isArray(initialBlocks)) { + initialBlocks.forEach(b => addBlockElement(b.type, b.value)); + } + } catch(e) { + // Fallback für alten Reintext aus Altbeständen + if (hiddenContentInput.value.trim() !== "") { + addBlockElement("text", hiddenContentInput.value); + } + } +}); diff --git a/php/controller/createArticle-controller.php b/php/controller/createArticle-controller.php index 53c7f3a..6c39386 100644 --- a/php/controller/createArticle-controller.php +++ b/php/controller/createArticle-controller.php @@ -64,10 +64,54 @@ 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); + } + + foreach ($blocks as &$block) { + // Base64-Format-Prüfung: + if ($block['type'] === 'image' && str_starts_with($block['value'], 'data:image/')) { + + // Base64-String zerlegen: + log_alert("Bild erkannt, verarbeite..."); + $parts = explode(',', $block['value']); + $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); + $articleManager->addArticle($title, $finalContent, $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"]); @@ -84,7 +128,4 @@ require_once '../validator/article-validator.php'; exit(); } } - - - ?> \ No newline at end of file diff --git a/php/model/ArticleManager.php b/php/model/ArticleManager.php index 435ac90..e20caa0 100644 --- a/php/model/ArticleManager.php +++ b/php/model/ArticleManager.php @@ -19,7 +19,7 @@ class ArticleManager public static function getInstance() { $articleManager = DatabaseArticleManager::getInstance(); // Hier kann zwischen dem lokalen und datenbankbasiertem ArticleManager gewechselt werden. - + /* // 100 fiktionale Fachbeiträge: $dummyArticles = [ // --- INFORMATIK & MATHE (1-20) --- @@ -154,7 +154,7 @@ class ArticleManager ); } } - + */ return $articleManager; } diff --git a/php/validator/article-validator.php b/php/validator/article-validator.php index d708258..2f4c877 100644 --- a/php/validator/article-validator.php +++ b/php/validator/article-validator.php @@ -33,19 +33,18 @@ function articleTitleValidator($title) } /** - * Prüft, ob der Contenttext 10-7000 Zeichen enthält. - * @param $content + * Prüft, ob der übergebene Content ein formal valider JSON-String ist. + * @param mixed $content Der zu prüfende Inhalt * @return bool */ function articleContentValidator($content) { - $content = trim($content); - $zeichenAnzahl = mb_strlen($content); - if ($zeichenAnzahl <= 7000 && $zeichenAnzahl >= 10) { - return true; - }else{ + if (!is_string($content)) { return false; } + + // Prüft direkt, ob der String valides JSON enthält + return json_validate($content); } /** From ec9bc3fe1f6a69b01cfd723d0ea1d3c89f7e6d3e Mon Sep 17 00:00:00 2001 From: NOrtmann1 Date: Sun, 14 Jun 2026 10:53:21 +0200 Subject: [PATCH 02/74] css --- content/createArticle.php | 25 ++++--- css/createArticle.css | 133 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 13 deletions(-) diff --git a/content/createArticle.php b/content/createArticle.php index 6554348..999ad0b 100644 --- a/content/createArticle.php +++ b/content/createArticle.php @@ -22,24 +22,23 @@ if (!isset($_SESSION["user"])) {
-
- - -

Noch keine Kommentare vorhanden.

+

+ Noch keine Kommentare vorhanden. +

diff --git a/index.php b/index.php index 1006322..5b5a7cb 100644 --- a/index.php +++ b/index.php @@ -29,55 +29,30 @@ if ($pfad === "deleteAccount") { } ?> - - - + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - + - EduForge - - - - - - - - - - \ No newline at end of file + EduForge + \ No newline at end of file diff --git a/js/comments.js b/js/comments.js new file mode 100644 index 0000000..7ee43f7 --- /dev/null +++ b/js/comments.js @@ -0,0 +1,55 @@ +document.addEventListener("DOMContentLoaded", function () { + const form = document.getElementById("comment-form"); + const commentsList = document.getElementById("comments-list"); + const commentContent = document.getElementById("comment-content"); + + if (!form || !commentsList || !commentContent) { + return; + } + + form.addEventListener("submit", function (event) { + event.preventDefault(); + + const formData = new FormData(form); + + fetch("php/ajax/add-comment.php", { + method: "POST", + body: formData + }) + .then(response => response.json()) + .then(data => { + if (!data.success) { + alert(data.message); + return; + } + + const emptyMessage = commentsList.querySelector(".no-comments-message"); + if (emptyMessage) { + emptyMessage.remove(); + } + + const commentElement = document.createElement("div"); + commentElement.classList.add("comment-item"); + + commentElement.innerHTML = ` +

+ ${escapeHtml(data.author)} + ${escapeHtml(data.created)} +

+

${escapeHtml(data.content).replace(/\n/g, "
")}

+ `; + + commentsList.prepend(commentElement); + commentContent.value = ""; + }) + .catch(() => { + alert("Kommentar konnte nicht gesendet werden."); + }); + }); + + function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } +}); \ No newline at end of file diff --git a/php/ajax/add-comment.php b/php/ajax/add-comment.php new file mode 100644 index 0000000..0895de3 --- /dev/null +++ b/php/ajax/add-comment.php @@ -0,0 +1,48 @@ + false, + "message" => "Du musst angemeldet sein." + ]); + exit(); +} + +$articleId = $_POST["article_id"] ?? null; +$content = trim($_POST["content"] ?? ""); + +if (empty($articleId) || empty($content)) { + echo json_encode([ + "success" => false, + "message" => "Kommentar darf nicht leer sein." + ]); + exit(); +} + +try { + $commentManager = CommentManager::getInstance(); + + $commentManager->addComment( + $articleId, + $_SESSION["user_email"], + $content + ); + + echo json_encode([ + "success" => true, + "author" => $_SESSION["user_email"], + "content" => $content, + "created" => date("Y-m-d H:i:s") + ]); + +} catch (Exception $e) { + echo json_encode([ + "success" => false, + "message" => "Kommentar konnte nicht gespeichert werden." + ]); +} From 21c5471d73f7362982852015a0cd378e71f3f9c9 Mon Sep 17 00:00:00 2001 From: Caroline Schulte Date: Mon, 15 Jun 2026 22:08:46 +0200 Subject: [PATCH 46/74] fehlerkorrektur --- index.php | 71 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/index.php b/index.php index 5b5a7cb..3cd00fb 100644 --- a/index.php +++ b/index.php @@ -29,30 +29,55 @@ if ($pfad === "deleteAccount") { } ?> - - - + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - + - EduForge - \ No newline at end of file + EduForge + + + + + + + + + + \ No newline at end of file From 59012cacfbbb86b62587f2c7ba0d59a280421561 Mon Sep 17 00:00:00 2001 From: Caroline Schulte Date: Mon, 15 Jun 2026 22:14:51 +0200 Subject: [PATCH 47/74] css design --- css/showArticle.css | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/css/showArticle.css b/css/showArticle.css index ca3b40a..cfe0aed 100644 --- a/css/showArticle.css +++ b/css/showArticle.css @@ -115,4 +115,66 @@ .article-view-title { font-size: 1.85rem; } + + /* --- KOMMENTARE --- */ + + .article-comments-section { + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid #e2e8f0; + } + + .article-comments-section h2 { + font-size: 2rem; + margin-bottom: 1.5rem; + color: #1a202c; + } + + .comment-item { + background-color: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 1rem 1.25rem; + margin-bottom: 1rem; + } + + .comment-item strong { + color: #1a202c; + } + + .comment-item span { + color: #64748b; + font-size: 0.9rem; + margin-left: 0.5rem; + } + + .comment-item p { + margin: 0.5rem 0; + } + + #comment-form { + margin-top: 2rem; + } + + #comment-content { + width: 100%; + min-height: 120px; + padding: 1rem; + border: 1px solid #cbd5e1; + border-radius: 8px; + font-size: 1rem; + font-family: inherit; + resize: vertical; + margin-bottom: 1rem; + } + + #comment-content:focus { + outline: none; + border-color: #2563eb; + } + + .no-comments-message { + color: #64748b; + font-style: italic; + } } From f13a2c6f1e4d12cd5dc0cb8da5e1db82f01955ba Mon Sep 17 00:00:00 2001 From: Caroline Schulte Date: Mon, 15 Jun 2026 22:17:44 +0200 Subject: [PATCH 48/74] css design --- css/showArticle.css | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/css/showArticle.css b/css/showArticle.css index cfe0aed..1539f90 100644 --- a/css/showArticle.css +++ b/css/showArticle.css @@ -115,6 +115,7 @@ .article-view-title { font-size: 1.85rem; } +} /* --- KOMMENTARE --- */ @@ -127,7 +128,10 @@ .article-comments-section h2 { font-size: 2rem; margin-bottom: 1.5rem; - color: #1a202c; + } + + #comments-list { + margin-bottom: 2rem; } .comment-item { @@ -138,43 +142,25 @@ margin-bottom: 1rem; } - .comment-item strong { - color: #1a202c; - } - - .comment-item span { - color: #64748b; - font-size: 0.9rem; - margin-left: 0.5rem; - } - - .comment-item p { - margin: 0.5rem 0; - } - #comment-form { - margin-top: 2rem; + width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; } #comment-content { - width: 100%; - min-height: 120px; + width: 100% !important; + min-height: 130px !important; padding: 1rem; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 1rem; font-family: inherit; resize: vertical; - margin-bottom: 1rem; } - #comment-content:focus { - outline: none; - border-color: #2563eb; + #comment-form .button { + width: 100%; } - .no-comments-message { - color: #64748b; - font-style: italic; - } -} From 6f6e53a483bbee4aa8358333108844f5a45791c5 Mon Sep 17 00:00:00 2001 From: Caroline Schulte Date: Mon, 15 Jun 2026 22:32:00 +0200 Subject: [PATCH 49/74] =?UTF-8?q?Antwortm=C3=B6glichkeit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/showArticle.php | 44 +++++++++-- js/comments.js | 106 +++++++++++++++++++++++++-- php/ajax/add-comment.php | 13 +++- php/model/Comment.php | 31 +++++++- php/model/CommentManagerDAO.php | 9 ++- php/model/DatabaseCommentManager.php | 39 ++++++++-- 6 files changed, 213 insertions(+), 29 deletions(-) diff --git a/content/showArticle.php b/content/showArticle.php index 3feb0bf..dbd4082 100644 --- a/content/showArticle.php +++ b/content/showArticle.php @@ -104,14 +104,38 @@ if (isset($_GET["id"])) {
-
-

- getAuthor()); ?> - getCreated()); ?> -

+ isReply()): ?> +
+

+ getAuthor()); ?> + getCreated()); ?> +

-

getContent())); ?>

-
+

getContent())); ?>

+ + + +
+ + getParentCommentId() === $comment->getId()): ?> +
+

+ getAuthor()); ?> + getCreated()); ?> +

+ +

getContent())); ?>

+
+ + +
+
+

@@ -125,6 +149,12 @@ if (isset($_GET["id"])) { "> + + +