diff --git a/content/search-results.php b/content/search-results.php index 85ed579..25b96be 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -64,8 +64,8 @@ $resultCount = count($results); Alphabetisch - > - Beliebtheit + onchange="this.form.submit()"> + Beliebtheit (Likes) > @@ -135,8 +135,14 @@ $resultCount = count($results); - Von: - Kategorie: + + Von: + + + ❤️ + + + → diff --git a/content/showArticle.php b/content/showArticle.php index d461d16..cd01df6 100644 --- a/content/showArticle.php +++ b/content/showArticle.php @@ -1,28 +1,10 @@ getCommentsByArticle($_GET["id"]); - - foreach ($comments as $comment) { - if ($comment->isReply()) { - $parentId = $comment->getParentCommentId(); - $repliesByParent[$parentId][] = $comment; - } else { - $mainComments[] = $comment; - } - } - } catch (Exception $e) { - $_SESSION["message"] = "internal_error"; - } -} +include_once 'php/controller/showArticle-controller.php'; ?> - - - + + + + + + + + + + + ❤️ getLikeCount(); ?> + + + + hasLiked($_SESSION["user_email"]) ? '👎 Gefällt mir nicht mehr' : '👍 Gefällt mir'; ?> + + + (Anmelden zum Liken) + + + + diff --git a/css/search-results.css b/css/search-results.css index 4fc5d97..a89817c 100644 --- a/css/search-results.css +++ b/css/search-results.css @@ -247,6 +247,17 @@ CSS für die Suchergebnis-Seite cursor: not-allowed; } +.s-res-meta-row { + display: flex; + gap: 15px; + align-items: center; +} + +.s-res-likes { + font-size: 0.9em; + color: #475569; +} + /* Responsive Anpassungen unter 760px (für z.B. Smartphones) */ @media (max-width: 768px) { .s-res-layout-grid { diff --git a/css/showArticle.css b/css/showArticle.css index 46ffbad..ec801d5 100644 --- a/css/showArticle.css +++ b/css/showArticle.css @@ -239,4 +239,48 @@ .comment-login-hint p { margin-bottom: 1rem; +} + +/* +Like-Button etc. + */ + +.category-and-likes-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +.article-view-likes { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.95em; +} + +.article-view-likes .like-count { + font-weight: bold; +} + +.article-view-likes .login-hint { + font-size: 0.8em; + color: #777; +} + +/* Interaktiver Like/Unlike-Button */ +.like-toggle-btn { + text-decoration: none; + padding: 4px 10px; + border: 1px solid #bbb; + border-radius: 4px; + background-color: #f5f5f5; + color: #333; + font-weight: 500; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.like-toggle-btn:hover { + background-color: #eaeaea; + border-color: #999; } \ No newline at end of file diff --git a/php/controller/like-controller.php b/php/controller/like-controller.php new file mode 100644 index 0000000..d0d9a95 --- /dev/null +++ b/php/controller/like-controller.php @@ -0,0 +1,42 @@ +toggleLike($articleId, $userEmail); + + header("Location: ../../index.php?pfad=showArticle&id=" . $articleId); + exit(); + + } catch (NotFoundException $e) { + $_SESSION["message"] = "missing_id"; + header("Location: ../../index.php"); + exit(); + } catch (Exception $e) { + $_SESSION["message"] = "internal_error"; + header("Location: ../../index.php"); + exit(); + } +} else { + $_SESSION["message"] = "missing_id"; + header("Location: ../../index.php"); + exit(); +} + +?> \ No newline at end of file diff --git a/php/controller/search-results-controller.php b/php/controller/search-results-controller.php index d4546e6..9907a69 100644 --- a/php/controller/search-results-controller.php +++ b/php/controller/search-results-controller.php @@ -26,18 +26,22 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) { if ($sortStyle === 'alphabet') { // Titel aufsteigend alphabetiisch sortiert + usort($results, function ($a, $b) { + return strcasecmp($a->getTitle(), $b->getTitle()); + }); + } elseif ($sortStyle === 'likes') { usort($results, function($a, $b) { - return strcasecmp($a->title, $b->title); + return $b->getLikeCount() <=> $a->getLikeCount(); }); } elseif ($sortStyle === 'newest') { // Datum neu zu alt sortiert usort($results, function($a, $b) { - return strcmp($b->creationDate, $a->creationDate); + return strcmp($b->getCreationDate(), $a->getCreationDate()); }); } elseif ($sortStyle === 'oldest') { // Datum alt zu neu sortiert usort($results, function($a, $b) { - return strcmp($a->creationDate, $b->creationDate); + return strcmp($a->getCreationDate(), $b->getCreationDate()); }); } @@ -45,14 +49,14 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) { $safeArrayResults = []; foreach ($results as $obj) { $safeArrayResults[] = [ - "id" => $obj->id, - "title" => $obj->title, - "content" => $obj->content, - "author" => $obj->author, - "category" => $obj->category, - "tags" => $obj->tags, - "creationDate" => $obj->creationDate - //"likes" => $obj->likes + "id" => $obj->getId(), + "title" => $obj->getTitle(), + "content" => $obj->getContent(), + "author" => $obj->getAuthor(), + "category" => $obj->getCategory(), + "tags" => $obj->getTags(), + "creationDate" => $obj->getCreationDate(), + "likes" => $obj->getLikes(), ]; } diff --git a/php/controller/showArticle-controller.php b/php/controller/showArticle-controller.php index cff0483..6e140f9 100644 --- a/php/controller/showArticle-controller.php +++ b/php/controller/showArticle-controller.php @@ -5,6 +5,7 @@ if (session_status() === PHP_SESSION_NONE) { require_once 'php/model/Article.php'; require_once 'php/model/ArticleManager.php'; +require_once 'php/model/CommentManager.php'; if (isset($_GET["id"]) && !empty($_GET["id"])){ try { @@ -17,11 +18,25 @@ if (isset($_GET["id"]) && !empty($_GET["id"])){ $category = $article->getCategory(); $author = $article->getAuthor(); $tags = $article->getTags(); + $articleObj = $article; // Objekt für die Like-Abfagen sichern }else{ //header("location: index.php?pfad=404"); include_once "content/404.php"; exit(); } + + $commentManager = CommentManager::getInstance(); + $comments = $commentManager->getCommentsByArticle($_GET["id"]); + + foreach ($comments as $comment) { + if ($comment->isReply()) { + $parentId = $comment->getParentCommentId(); + $repliesByParent[$parentId][] = $comment; + } else { + $mainComments[] = $comment; + } + } + } catch (Exception $e){ $_SESSION["message"] = "internal_error"; exit(); diff --git a/php/model/Article.php b/php/model/Article.php index 4b1d492..a0e1b65 100644 --- a/php/model/Article.php +++ b/php/model/Article.php @@ -7,13 +7,14 @@ */ class Article { - public $id; - public $title; - public $content; - public $author; - public $creationDate; - public $category; - public $tags; + private $id; + private $title; + private $content; + private $author; + private $creationDate; + private $category; + private $tags; + private $likes; /** * Konstruktor @@ -26,7 +27,7 @@ class Article * @param $tags string optionale Schlagworte für eine bessere Suche * @param $creationDate string Datum der Beitragserstellung */ - public function __construct(int $id, string $title, string $content, string $author, string $category, string $tags, string $creationDate) + public function __construct(int $id, string $title, string $content, string $author, string $category, string $tags, string $creationDate, array $likes = []) { $this->id = $id; $this->title = $title; @@ -35,6 +36,7 @@ class Article $this->creationDate = $creationDate; $this->category = $category; $this->tags = $tags; + $this->likes = $likes; } /** @@ -67,7 +69,7 @@ class Article /** * Gibt den Content eines Beitrags zurück. - * TODO: Content muss noch definiert werden. + * * @return string */ public function getContent(): string @@ -77,7 +79,7 @@ class Article /** * Setzt den Content eines Beitrags. - * TODO: Content muss noch definiert werden. + * * @param $content * @return void */ @@ -141,7 +143,34 @@ class Article $this->tags = $tags; } + /** + * Gibt alle User-IDs zurück, die diesen Beitrag geliked haben. + * @return array + */ + public function getLikes(): array + { + return $this->likes; + } + /** + * Gibt die Gesamtzahl der Likes zurück. + * @return int + */ + public function getLikeCount(): int + { + return count($this->likes); + } + + /** + * Prüft, ob ein bestimmter Nutzer den Beitrag bereits geliked hat. + * + * @param string $userId + * @return bool + */ + public function hasLiked(string $userId): bool + { + return in_array($userId, $this->likes); + } } ?> \ No newline at end of file diff --git a/php/model/ArticleManagerDAO.php b/php/model/ArticleManagerDAO.php index 6dcf7e2..fea3d0c 100644 --- a/php/model/ArticleManagerDAO.php +++ b/php/model/ArticleManagerDAO.php @@ -92,6 +92,18 @@ interface ArticleManagerDAO */ public function getArticlesByCategory($category); + /** + * Registriert oder entfernt ein Like eines Nutzers für einen Beitrag. + * Wenn schon geliked -> Unlike + * Wenn noch nicht geliked -> Like + * + * @param int $articleId Die ID des Beitrags + * @param string $userId Die ID des Nutzers + * @return bool True wenn geliked, False wenn unliked + * @throws InternalServerErrorException + * @throws NotFoundException + */ + public function toggleLike(int $articleId, string $userId): bool; } ?> \ No newline at end of file diff --git a/php/model/DatabaseArticleManager.php b/php/model/DatabaseArticleManager.php index 7d9f31b..ad49ea4 100644 --- a/php/model/DatabaseArticleManager.php +++ b/php/model/DatabaseArticleManager.php @@ -23,6 +23,7 @@ class DatabaseArticleManager implements ArticleManagerDAO { $db = $this->getConnection(); + // Tabelle für Beiträge $db->exec(" CREATE TABLE articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -33,6 +34,15 @@ class DatabaseArticleManager implements ArticleManagerDAO { tags TEXT, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP );"); + + // Tabelle für Likes + $db->exec(" + CREATE TABLE likes ( + article_id INTEGER, + user_id TEXT, + PRIMARY KEY (article_id, user_id), + FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE + );"); unset($db); } catch (PDOException $e) { throw new InternalServerErrorException($e->getMessage()); @@ -189,6 +199,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $row = $command->fetch(PDO::FETCH_ASSOC); if ($row) { + $likes = $this->getLikesForArticle(intval($row['id'])); + return new Article( intval($row['id']), $row['title'], @@ -196,7 +208,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $row['author'], $row['category'], $row['tags'], - $row['created'] + $row['created'], + $likes ); } @@ -254,6 +267,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $filteredArticles = []; foreach ($rows as $row) { + $likes = $this->getLikesForArticle(intval($row['id'])); + $filteredArticles[] = new Article( intval($row['id']), $row['title'], @@ -261,7 +276,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $row['author'], $row['category'], $row['tags'], - $row['created'] + $row['created'], + $likes ); } @@ -287,6 +303,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $filteredArticles = []; foreach ($rows as $row) { + $likes = $this->getLikesForArticle(intval($row['id'])); + $filteredArticles[] = new Article( intval($row['id']), $row['title'], @@ -294,7 +312,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $row['author'], $row['category'], $row['tags'], - $row['created'] + $row['created'], + $likes ); } @@ -341,6 +360,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $filteredArticles = []; foreach ($rows as $row) { + $likes = $this->getLikesForArticle(intval($row['id'])); + $filteredArticles[] = new Article( intval($row['id']), $row['title'] ?? '', @@ -348,7 +369,8 @@ class DatabaseArticleManager implements ArticleManagerDAO { $row['author'] ?? '', $row['category'] ?? '', $row['tags'] ?? '', - $row['created'] ?? '' // Nutzt 'created' aus deiner DB-Struktur + $row['created'] ?? '', + $likes ); } @@ -359,4 +381,69 @@ class DatabaseArticleManager implements ArticleManagerDAO { } } + /** + * Holt alle User-IDs, die einen bestimmten Beitrag geliked haben. + * + * @return String[] UserIDs + * @throws InternalServerErrorException + */ + private function getLikesForArticle(int $articleId): array + { + try { + $db = $this->getConnection(); + $sql = "SELECT user_id FROM likes WHERE article_id = :article_id;"; + $command = $db->prepare($sql); + $command->execute([':article_id' => $articleId]); + + return $command->fetchAll(PDO::FETCH_COLUMN) ?: []; + } catch (PDOException $e) { + return []; + } + } + + public function toggleLike(int $articleId, string $userId): bool + { + // prüfen, ob der Artikel überhaupt existiert + $article = $this->getArticle($articleId); + if (!$article) { + throw new NotFoundException("missing_id"); + } + + try { + $db = $this->getConnection(); + + // prüfen, ob das Like bereits existiert + $checkSql = "SELECT COUNT(*) FROM likes WHERE article_id = :article_id AND user_id = :user_id;"; + $checkCommand = $db->prepare($checkSql); + $checkCommand->execute([ + ':article_id' => $articleId, + ':user_id' => $userId + ]); + + $hasLiked = (int)$checkCommand->fetchColumn() > 0; + + if ($hasLiked) { + // wenn bereits geliked -> Unlike + $deleteSql = "DELETE FROM likes WHERE article_id = :article_id AND user_id = :user_id;"; + $deleteCommand = $db->prepare($deleteSql); + $deleteCommand->execute([ + ':article_id' => $articleId, + ':user_id' => $userId + ]); + return false; // gibt false zurück, da der Beitrag jetzt nicht mehr geliked ist + } else { + // wenn noch nicht geliked -> Like + $insertSql = "INSERT INTO likes (article_id, user_id) VALUES (:article_id, :user_id);"; + $insertCommand = $db->prepare($insertSql); + $insertCommand->execute([ + ':article_id' => $articleId, + ':user_id' => $userId + ]); + return true; // gibt true zurück, da der Beitrag jetzt geliked ist + } + + } catch (PDOException $e) { + throw new InternalServerErrorException("internal_error"); + } + } } \ No newline at end of file diff --git a/php/model/LocalArticleManager.php b/php/model/LocalArticleManager.php index 6df27e9..aab081a 100644 --- a/php/model/LocalArticleManager.php +++ b/php/model/LocalArticleManager.php @@ -62,7 +62,8 @@ class LocalArticleManager implements ArticleManagerDAO { "author" => $author, "category" => $category, "tags" => $tags, - "creationDate" => date("Y-m-d H:i:s") + "creationDate" => date("Y-m-d H:i:s"), + "likes" => [] ]; $this->saveArticle($articles); @@ -92,7 +93,8 @@ class LocalArticleManager implements ArticleManagerDAO { "author" => $author, "category" => $article->getCategory(), "tags" => $article->getTags(), - "creationDate" => $article->getCreationDate() + "creationDate" => $article->getCreationDate(), + "likes" => $storedArticle['likes'] ?? [] ]; $updated = true; break; @@ -142,7 +144,17 @@ class LocalArticleManager implements ArticleManagerDAO { foreach ($articles as $article) { if (isset($article['id']) && $article['id'] == $id) { - return new Article(intval($article['id']), $article['title'], $article['content'], $article['author'], $article['category'], $article['tags'], $article['creationDate']); + $likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : []; + return new Article( + intval($article['id']), + $article['title'], + $article['content'], + $article['author'], + $article['category'], + $article['tags'], + $article['creationDate'], + $likes + ); } } @@ -168,6 +180,7 @@ class LocalArticleManager implements ArticleManagerDAO { foreach ($articles as $article) { if (isset($article['author']) && $article['author'] == $author) { + $likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : []; $filteredArticles[] = new Article( intval($article['id']), $article['title'], @@ -175,7 +188,8 @@ class LocalArticleManager implements ArticleManagerDAO { $article['author'], $article['category'], $article['tags'], - $article['creationDate'] + $article['creationDate'], + $likes ); } } @@ -201,6 +215,7 @@ class LocalArticleManager implements ArticleManagerDAO { if (($cleanKeyword !== '' && strpos($title, $cleanKeyword) !== false) || ($cleanKeyword !== '' && strpos($content, $cleanKeyword) !== false)) { + $likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : []; $filteredArticles[] = new Article( intval($article['id'] ?? 0), $article['title'] ?? '', @@ -208,7 +223,8 @@ class LocalArticleManager implements ArticleManagerDAO { $article['author'] ?? '', $article['category'] ?? '', $article['tags'] ?? '', - $article['creationDate'] ?? '' + $article['creationDate'] ?? '', + $likes ); } } @@ -223,6 +239,7 @@ class LocalArticleManager implements ArticleManagerDAO { foreach ($articles as $article) { if (isset($article['category']) && $article['category'] == $category) { + $likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : []; $filteredArticles[] = new Article( intval($article['id']), $article['title'], @@ -230,12 +247,53 @@ class LocalArticleManager implements ArticleManagerDAO { $article['author'], $article['category'], $article['tags'], - $article['creationDate'] + $article['creationDate'], + $likes ); } } return $filteredArticles; } + public function toggleLike(int $articleId, string $userId): bool + { + $articles = $this->getAllArticles(); + $articleFound = false; + $isLikedNow = false; + + foreach ($articles as $index => $article) { + if (isset($article['id']) && $article['id'] == $articleId) { + $articleFound = true; + + // Likes-Array initialisieren, falls nicht vorhanden + if (!isset($articles[$index]['likes']) || !is_array($articles[$index]['likes'])) { + $articles[$index]['likes'] = []; + } + + $likeIndex = array_search($userId, $articles[$index]['likes']); + + if ($likeIndex !== false) { + // Bereits geliked -> Unlike + unset($articles[$index]['likes'][$likeIndex]); + // Array-Keys neu indizieren, damit JSON sauber bleibt + $articles[$index]['likes'] = array_values($articles[$index]['likes']); + $isLikedNow = false; + } else { + // Noch nicht geliked -> Like (User-ID hinzufügen) + $articles[$index]['likes'][] = $userId; + $isLikedNow = true; + } + break; + } + } + + if (!$articleFound) { + throw new NotFoundException("missing_id"); + } + + $this->saveArticle($articles); + return $isLikedNow; + } + } ?> \ No newline at end of file
Von:
Kategorie: