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">
<?php if (!empty($comments)): ?>
<?php foreach ($comments as $comment): ?>
<div class="comment-item">
<p>
<strong><?php echo htmlspecialchars($comment->getAuthor()); ?></strong>
<span><?php echo htmlspecialchars($comment->getCreated()); ?></span>
</p>
<?php if (!$comment->isReply()): ?>
<div class="comment-item" data-comment-id="<?php echo htmlspecialchars($comment->getId()); ?>">
<p>
<strong><?php echo htmlspecialchars($comment->getAuthor()); ?></strong>
<span><?php echo htmlspecialchars($comment->getCreated()); ?></span>
</p>
<p><?php echo nl2br(htmlspecialchars($comment->getContent())); ?></p>
</div>
<p><?php echo nl2br(htmlspecialchars($comment->getContent())); ?></p>
<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 else: ?>
<p class="no-comments-message">
@@ -125,6 +149,12 @@ if (isset($_GET["id"])) {
<input type="hidden"
name="article_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"
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 () {
const form = document.getElementById("comment-form");
const commentsList = document.getElementById("comments-list");
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;
}
/**
* 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) {
event.preventDefault();
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", {
method: "POST",
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.message);
return;
}
const emptyMessage = commentsList.querySelector(".no-comments-message");
const emptyMessage =
commentsList.querySelector(".no-comments-message");
if (emptyMessage) {
emptyMessage.remove();
}
const commentElement = document.createElement("div");
const commentElement =
document.createElement("div");
commentElement.classList.add("comment-item");
if (parentCommentId) {
commentElement.classList.add("comment-reply");
}
commentElement.innerHTML = `
<p>
<strong>${escapeHtml(data.author)}</strong>
@@ -39,17 +92,60 @@ document.addEventListener("DOMContentLoaded", function () {
<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 = "";
parentCommentInput.value = "";
replyInfo.textContent = "";
replyInfo.style.display = "none";
})
.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) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
});
+10 -3
View File
@@ -15,6 +15,11 @@ if (!isset($_SESSION["user_email"])) {
$articleId = $_POST["article_id"] ?? null;
$content = trim($_POST["content"] ?? "");
$parentCommentId = $_POST["parent_comment_id"] ?? null;
if ($parentCommentId === "") {
$parentCommentId = null;
}
if (empty($articleId) || empty($content)) {
echo json_encode([
@@ -30,14 +35,16 @@ try {
$commentManager->addComment(
$articleId,
$_SESSION["user_email"],
$content
$content,
$parentCommentId
);
echo json_encode([
"success" => true,
"author" => $_SESSION["user_email"],
"content" => $content,
"created" => date("Y-m-d H:i:s")
"created" => date("Y-m-d H:i:s"),
"parentCommentId" => $parentCommentId
]);
} catch (Exception $e) {
@@ -45,4 +52,4 @@ try {
"success" => false,
"message" => "Kommentar konnte nicht gespeichert werden."
]);
}
}
+27 -4
View File
@@ -3,9 +3,8 @@
/**
* Repräsentiert einen Kommentar unter einem Beitrag.
*
* Ein Kommentar besteht aus einer eindeutigen ID,
* der ID des zugehörigen Beitrags, dem Autor,
* dem Inhalt sowie dem Erstellungsdatum.
* Ein Kommentar kann entweder ein Hauptkommentar sein
* oder eine Antwort auf einen anderen Kommentar.
*
* @author Caroline Schulte
*/
@@ -13,6 +12,7 @@ class Comment
{
private int $id;
private int $articleId;
private ?int $parentCommentId;
private string $author;
private string $content;
private string $created;
@@ -22,6 +22,7 @@ class Comment
*
* @param int $id Eindeutige ID des Kommentars
* @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 $content Inhalt des Kommentars
* @param string $created Erstellungsdatum des Kommentars
@@ -29,12 +30,14 @@ class Comment
public function __construct(
int $id,
int $articleId,
?int $parentCommentId,
string $author,
string $content,
string $created
) {
$this->id = $id;
$this->articleId = $articleId;
$this->parentCommentId = $parentCommentId;
$this->author = $author;
$this->content = $content;
$this->created = $created;
@@ -60,6 +63,26 @@ class Comment
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.
*
@@ -89,4 +112,4 @@ class Comment
{
return $this->created;
}
}
}
+7 -2
View File
@@ -15,16 +15,21 @@ interface CommentManagerDAO
/**
* 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 string $author Autor des Kommentars
* @param string $content Inhalt des Kommentars
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
*
* @return void
*/
public function addComment(
$articleId,
$author,
$content
$content,
$parentCommentId = null
);
/**
@@ -37,4 +42,4 @@ interface CommentManagerDAO
public function getCommentsByArticle(
$articleId
);
}
}
+31 -8
View File
@@ -16,6 +16,8 @@ class DatabaseCommentManager implements CommentManagerDAO
/**
* Erstellt die Kommentartabelle,
* falls diese noch nicht existiert.
*
* Zusätzlich wird geprüft, ob die Spalte parent_comment_id existiert.
*/
public function __construct()
{
@@ -26,12 +28,27 @@ class DatabaseCommentManager implements CommentManagerDAO
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
article_id INTEGER NOT NULL,
parent_comment_id INTEGER NULL,
author TEXT NOT NULL,
content TEXT NOT NULL,
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);
} catch (PDOException $e) {
@@ -78,19 +95,20 @@ class DatabaseCommentManager implements CommentManagerDAO
}
/**
* Speichert einen neuen Kommentar
* zu einem Beitrag.
* Speichert einen neuen Kommentar oder eine Antwort.
*
* @param int $articleId ID des Beitrags
* @param string $author Autor des Kommentars
* @param string $content Inhalt des Kommentars
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
*
* @return void
*/
public function addComment(
$articleId,
$author,
$content
$content,
$parentCommentId = null
) {
try {
$db = $this->getConnection();
@@ -98,11 +116,13 @@ class DatabaseCommentManager implements CommentManagerDAO
$sql = "
INSERT INTO comments (
article_id,
parent_comment_id,
author,
content
)
VALUES (
:articleId,
:parentCommentId,
:author,
:content
)
@@ -112,6 +132,7 @@ class DatabaseCommentManager implements CommentManagerDAO
$command->execute([
":articleId" => $articleId,
":parentCommentId" => $parentCommentId,
":author" => $author,
":content" => $content
]);
@@ -140,7 +161,7 @@ class DatabaseCommentManager implements CommentManagerDAO
SELECT *
FROM comments
WHERE article_id = :articleId
ORDER BY created DESC
ORDER BY created ASC
";
$command = $db->prepare($sql);
@@ -152,10 +173,12 @@ class DatabaseCommentManager implements CommentManagerDAO
$comments = [];
while ($row = $command->fetch(PDO::FETCH_ASSOC)) {
$comments[] = new Comment(
$row["id"],
$row["article_id"],
intval($row["id"]),
intval($row["article_id"]),
isset($row["parent_comment_id"]) && $row["parent_comment_id"] !== null
? intval($row["parent_comment_id"])
: null,
$row["author"],
$row["content"],
$row["created"]
@@ -170,4 +193,4 @@ class DatabaseCommentManager implements CommentManagerDAO
);
}
}
}
}