Antwortmöglichkeit

This commit is contained in:
2026-06-15 22:32:00 +02:00
parent f13a2c6f1e
commit 6f6e53a483
6 changed files with 213 additions and 29 deletions
+37 -7
View File
@@ -104,14 +104,38 @@ if (isset($_GET["id"])) {
<div id="comments-list"> <div id="comments-list">
<?php if (!empty($comments)): ?> <?php if (!empty($comments)): ?>
<?php foreach ($comments as $comment): ?> <?php foreach ($comments as $comment): ?>
<div class="comment-item"> <?php if (!$comment->isReply()): ?>
<p> <div class="comment-item" data-comment-id="<?php echo htmlspecialchars($comment->getId()); ?>">
<strong><?php echo htmlspecialchars($comment->getAuthor()); ?></strong> <p>
<span><?php echo htmlspecialchars($comment->getCreated()); ?></span> <strong><?php echo htmlspecialchars($comment->getAuthor()); ?></strong>
</p> <span><?php echo htmlspecialchars($comment->getCreated()); ?></span>
</p>
<p><?php echo nl2br(htmlspecialchars($comment->getContent())); ?></p> <p><?php echo nl2br(htmlspecialchars($comment->getContent())); ?></p>
</div>
<button type="button"
class="reply-button"
data-comment-id="<?php echo htmlspecialchars($comment->getId()); ?>"
data-author="<?php echo htmlspecialchars($comment->getAuthor()); ?>">
Antworten
</button>
<div class="comment-replies">
<?php foreach ($comments as $reply): ?>
<?php if ($reply->getParentCommentId() === $comment->getId()): ?>
<div class="comment-item comment-reply">
<p>
<strong><?php echo htmlspecialchars($reply->getAuthor()); ?></strong>
<span><?php echo htmlspecialchars($reply->getCreated()); ?></span>
</p>
<p><?php echo nl2br(htmlspecialchars($reply->getContent())); ?></p>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
<p class="no-comments-message"> <p class="no-comments-message">
@@ -125,6 +149,12 @@ if (isset($_GET["id"])) {
<input type="hidden" <input type="hidden"
name="article_id" name="article_id"
value="<?php echo htmlspecialchars($_GET["id"] ?? ""); ?>"> value="<?php echo htmlspecialchars($_GET["id"] ?? ""); ?>">
<input type="hidden"
name="parent_comment_id"
id="parent-comment-id"
value="">
<p id="reply-info" class="reply-info" style="display: none;"></p>
<textarea name="content" <textarea name="content"
id="comment-content" id="comment-content"
+101 -5
View File
@@ -1,36 +1,89 @@
/**
* Initialisiert die Kommentarfunktion.
*
* Ermöglicht:
* - Erstellen neuer Kommentare
* - Antworten auf bestehende Kommentare
* - AJAX-Kommunikation ohne Seitenneuladen
*/
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("comment-form"); const form = document.getElementById("comment-form");
const commentsList = document.getElementById("comments-list"); const commentsList = document.getElementById("comments-list");
const commentContent = document.getElementById("comment-content"); const commentContent = document.getElementById("comment-content");
const parentCommentInput = document.getElementById("parent-comment-id");
const replyInfo = document.getElementById("reply-info");
if (!form || !commentsList || !commentContent) { if (!form || !commentsList || !commentContent || !parentCommentInput || !replyInfo) {
return; return;
} }
/**
* Registriert die Antwort-Buttons.
*
* Beim Klick wird die ID des Eltern-Kommentars gespeichert,
* damit die neue Nachricht als Antwort angelegt werden kann.
*/
document.querySelectorAll(".reply-button").forEach(function (button) {
button.addEventListener("click", function () {
parentCommentInput.value = button.dataset.commentId;
replyInfo.textContent =
"Antwort auf " + button.dataset.author;
replyInfo.style.display = "block";
commentContent.focus();
});
});
/**
* Verarbeitet das Absenden eines Kommentars.
*
* Die Daten werden per AJAX an den Server gesendet,
* sodass die Seite nicht neu geladen werden muss.
*/
form.addEventListener("submit", function (event) { form.addEventListener("submit", function (event) {
event.preventDefault(); event.preventDefault();
const formData = new FormData(form); const formData = new FormData(form);
const parentCommentId = parentCommentInput.value;
/*
* Sendet den Kommentar an den Server
* und speichert ihn in der Datenbank.
*/
fetch("php/ajax/add-comment.php", { fetch("php/ajax/add-comment.php", {
method: "POST", method: "POST",
body: formData body: formData
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
alert(data.message); alert(data.message);
return; return;
} }
const emptyMessage = commentsList.querySelector(".no-comments-message"); const emptyMessage =
commentsList.querySelector(".no-comments-message");
if (emptyMessage) { if (emptyMessage) {
emptyMessage.remove(); emptyMessage.remove();
} }
const commentElement = document.createElement("div"); const commentElement =
document.createElement("div");
commentElement.classList.add("comment-item"); commentElement.classList.add("comment-item");
if (parentCommentId) {
commentElement.classList.add("comment-reply");
}
commentElement.innerHTML = ` commentElement.innerHTML = `
<p> <p>
<strong>${escapeHtml(data.author)}</strong> <strong>${escapeHtml(data.author)}</strong>
@@ -39,17 +92,60 @@ document.addEventListener("DOMContentLoaded", function () {
<p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p> <p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p>
`; `;
commentsList.prepend(commentElement); /*
* Antworten werden unter dem
* zugehörigen Kommentar angezeigt.
*/
if (parentCommentId) {
const parentComment = document.querySelector(
`.comment-item[data-comment-id="${parentCommentId}"] .comment-replies`
);
if (parentComment) {
parentComment.appendChild(commentElement);
}
} else {
/*
* Normale Kommentare werden
* oben in die Liste eingefügt.
*/
commentElement.dataset.commentId = "";
commentsList.prepend(commentElement);
}
/*
* Formular zurücksetzen.
*/
commentContent.value = ""; commentContent.value = "";
parentCommentInput.value = "";
replyInfo.textContent = "";
replyInfo.style.display = "none";
}) })
.catch(() => { .catch(() => {
alert("Kommentar konnte nicht gesendet werden.");
alert(
"Kommentar konnte nicht gesendet werden."
);
}); });
}); });
/**
* Escaped HTML-Sonderzeichen zur Vermeidung
* von XSS-Angriffen.
*
* @param {string} text Zu bereinigender Text
* @returns {string} HTML-sicherer Text
*/
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement("div"); const div = document.createElement("div");
div.textContent = text; div.textContent = text;
return div.innerHTML; return div.innerHTML;
} }
}); });
+10 -3
View File
@@ -15,6 +15,11 @@ if (!isset($_SESSION["user_email"])) {
$articleId = $_POST["article_id"] ?? null; $articleId = $_POST["article_id"] ?? null;
$content = trim($_POST["content"] ?? ""); $content = trim($_POST["content"] ?? "");
$parentCommentId = $_POST["parent_comment_id"] ?? null;
if ($parentCommentId === "") {
$parentCommentId = null;
}
if (empty($articleId) || empty($content)) { if (empty($articleId) || empty($content)) {
echo json_encode([ echo json_encode([
@@ -30,14 +35,16 @@ try {
$commentManager->addComment( $commentManager->addComment(
$articleId, $articleId,
$_SESSION["user_email"], $_SESSION["user_email"],
$content $content,
$parentCommentId
); );
echo json_encode([ echo json_encode([
"success" => true, "success" => true,
"author" => $_SESSION["user_email"], "author" => $_SESSION["user_email"],
"content" => $content, "content" => $content,
"created" => date("Y-m-d H:i:s") "created" => date("Y-m-d H:i:s"),
"parentCommentId" => $parentCommentId
]); ]);
} catch (Exception $e) { } catch (Exception $e) {
@@ -45,4 +52,4 @@ try {
"success" => false, "success" => false,
"message" => "Kommentar konnte nicht gespeichert werden." "message" => "Kommentar konnte nicht gespeichert werden."
]); ]);
} }
+27 -4
View File
@@ -3,9 +3,8 @@
/** /**
* Repräsentiert einen Kommentar unter einem Beitrag. * Repräsentiert einen Kommentar unter einem Beitrag.
* *
* Ein Kommentar besteht aus einer eindeutigen ID, * Ein Kommentar kann entweder ein Hauptkommentar sein
* der ID des zugehörigen Beitrags, dem Autor, * oder eine Antwort auf einen anderen Kommentar.
* dem Inhalt sowie dem Erstellungsdatum.
* *
* @author Caroline Schulte * @author Caroline Schulte
*/ */
@@ -13,6 +12,7 @@ class Comment
{ {
private int $id; private int $id;
private int $articleId; private int $articleId;
private ?int $parentCommentId;
private string $author; private string $author;
private string $content; private string $content;
private string $created; private string $created;
@@ -22,6 +22,7 @@ class Comment
* *
* @param int $id Eindeutige ID des Kommentars * @param int $id Eindeutige ID des Kommentars
* @param int $articleId ID des zugehörigen Beitrags * @param int $articleId ID des zugehörigen Beitrags
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
* @param string $author Autor des Kommentars * @param string $author Autor des Kommentars
* @param string $content Inhalt des Kommentars * @param string $content Inhalt des Kommentars
* @param string $created Erstellungsdatum des Kommentars * @param string $created Erstellungsdatum des Kommentars
@@ -29,12 +30,14 @@ class Comment
public function __construct( public function __construct(
int $id, int $id,
int $articleId, int $articleId,
?int $parentCommentId,
string $author, string $author,
string $content, string $content,
string $created string $created
) { ) {
$this->id = $id; $this->id = $id;
$this->articleId = $articleId; $this->articleId = $articleId;
$this->parentCommentId = $parentCommentId;
$this->author = $author; $this->author = $author;
$this->content = $content; $this->content = $content;
$this->created = $created; $this->created = $created;
@@ -60,6 +63,26 @@ class Comment
return $this->articleId; return $this->articleId;
} }
/**
* Gibt die ID des Eltern-Kommentars zurück.
*
* @return int|null ID des Eltern-Kommentars oder null
*/
public function getParentCommentId(): ?int
{
return $this->parentCommentId;
}
/**
* Gibt zurück, ob der Kommentar eine Antwort ist.
*
* @return bool true wenn der Kommentar eine Antwort ist, sonst false
*/
public function isReply(): bool
{
return $this->parentCommentId !== null;
}
/** /**
* Gibt den Autor des Kommentars zurück. * Gibt den Autor des Kommentars zurück.
* *
@@ -89,4 +112,4 @@ class Comment
{ {
return $this->created; return $this->created;
} }
} }
+7 -2
View File
@@ -15,16 +15,21 @@ interface CommentManagerDAO
/** /**
* Speichert einen neuen Kommentar zu einem Beitrag. * Speichert einen neuen Kommentar zu einem Beitrag.
* *
* Optional kann eine parentCommentId übergeben werden,
* wenn der Kommentar eine Antwort auf einen anderen Kommentar ist.
*
* @param int $articleId ID des Beitrags * @param int $articleId ID des Beitrags
* @param string $author Autor des Kommentars * @param string $author Autor des Kommentars
* @param string $content Inhalt des Kommentars * @param string $content Inhalt des Kommentars
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
* *
* @return void * @return void
*/ */
public function addComment( public function addComment(
$articleId, $articleId,
$author, $author,
$content $content,
$parentCommentId = null
); );
/** /**
@@ -37,4 +42,4 @@ interface CommentManagerDAO
public function getCommentsByArticle( public function getCommentsByArticle(
$articleId $articleId
); );
} }
+31 -8
View File
@@ -16,6 +16,8 @@ class DatabaseCommentManager implements CommentManagerDAO
/** /**
* Erstellt die Kommentartabelle, * Erstellt die Kommentartabelle,
* falls diese noch nicht existiert. * falls diese noch nicht existiert.
*
* Zusätzlich wird geprüft, ob die Spalte parent_comment_id existiert.
*/ */
public function __construct() public function __construct()
{ {
@@ -26,12 +28,27 @@ class DatabaseCommentManager implements CommentManagerDAO
CREATE TABLE IF NOT EXISTS comments ( CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
article_id INTEGER NOT NULL, article_id INTEGER NOT NULL,
parent_comment_id INTEGER NULL,
author TEXT NOT NULL, author TEXT NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
"); ");
$columns = $db->query("PRAGMA table_info(comments);")->fetchAll(PDO::FETCH_ASSOC);
$hasParentColumn = false;
foreach ($columns as $column) {
if ($column["name"] === "parent_comment_id") {
$hasParentColumn = true;
break;
}
}
if (!$hasParentColumn) {
$db->exec("ALTER TABLE comments ADD COLUMN parent_comment_id INTEGER NULL;");
}
unset($db); unset($db);
} catch (PDOException $e) { } catch (PDOException $e) {
@@ -78,19 +95,20 @@ class DatabaseCommentManager implements CommentManagerDAO
} }
/** /**
* Speichert einen neuen Kommentar * Speichert einen neuen Kommentar oder eine Antwort.
* zu einem Beitrag.
* *
* @param int $articleId ID des Beitrags * @param int $articleId ID des Beitrags
* @param string $author Autor des Kommentars * @param string $author Autor des Kommentars
* @param string $content Inhalt des Kommentars * @param string $content Inhalt des Kommentars
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
* *
* @return void * @return void
*/ */
public function addComment( public function addComment(
$articleId, $articleId,
$author, $author,
$content $content,
$parentCommentId = null
) { ) {
try { try {
$db = $this->getConnection(); $db = $this->getConnection();
@@ -98,11 +116,13 @@ class DatabaseCommentManager implements CommentManagerDAO
$sql = " $sql = "
INSERT INTO comments ( INSERT INTO comments (
article_id, article_id,
parent_comment_id,
author, author,
content content
) )
VALUES ( VALUES (
:articleId, :articleId,
:parentCommentId,
:author, :author,
:content :content
) )
@@ -112,6 +132,7 @@ class DatabaseCommentManager implements CommentManagerDAO
$command->execute([ $command->execute([
":articleId" => $articleId, ":articleId" => $articleId,
":parentCommentId" => $parentCommentId,
":author" => $author, ":author" => $author,
":content" => $content ":content" => $content
]); ]);
@@ -140,7 +161,7 @@ class DatabaseCommentManager implements CommentManagerDAO
SELECT * SELECT *
FROM comments FROM comments
WHERE article_id = :articleId WHERE article_id = :articleId
ORDER BY created DESC ORDER BY created ASC
"; ";
$command = $db->prepare($sql); $command = $db->prepare($sql);
@@ -152,10 +173,12 @@ class DatabaseCommentManager implements CommentManagerDAO
$comments = []; $comments = [];
while ($row = $command->fetch(PDO::FETCH_ASSOC)) { while ($row = $command->fetch(PDO::FETCH_ASSOC)) {
$comments[] = new Comment( $comments[] = new Comment(
$row["id"], intval($row["id"]),
$row["article_id"], intval($row["article_id"]),
isset($row["parent_comment_id"]) && $row["parent_comment_id"] !== null
? intval($row["parent_comment_id"])
: null,
$row["author"], $row["author"],
$row["content"], $row["content"],
$row["created"] $row["created"]
@@ -170,4 +193,4 @@ class DatabaseCommentManager implements CommentManagerDAO
); );
} }
} }
} }