Antwortmöglichkeit
This commit is contained in:
+37
-7
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -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) {
|
||||||
|
|||||||
+26
-3
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
Reference in New Issue
Block a user