From 66eeac372ca3163927959d3a166408c136ce47bb Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 14:41:05 +0200 Subject: [PATCH 01/22] Anpassung der results seite und erstellung der script-datei --- content/search-results.php | 26 ++++++++++++-------------- js/sorter.js | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 js/sorter.js diff --git a/content/search-results.php b/content/search-results.php index c7ff9e4..97ff8b6 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -44,12 +44,11 @@ $resultCount = count($results); @@ -94,7 +127,7 @@ $resultCount = count($results); if (!empty($results)): ?> -
+

@@ -102,7 +135,7 @@ $resultCount = count($results);

Von:

- +

📁

👍 Likes

diff --git a/index.php b/index.php index 0c2be41..6064334 100644 --- a/index.php +++ b/index.php @@ -56,6 +56,7 @@ if ($pfad === "deleteAccount") { + EduForge diff --git a/js/filter.js b/js/filter.js new file mode 100644 index 0000000..ba972a0 --- /dev/null +++ b/js/filter.js @@ -0,0 +1,29 @@ + +function initFilter() { + const filterSelect = document.getElementById('category-filter'); + const listContainer = document.querySelector('.s-res-list'); + + if (!filterSelect || !listContainer) return; + + filterSelect.addEventListener('change', function() { + const selectedCategory = this.value; + const cards = listContainer.querySelectorAll('.s-res-item'); + + cards.forEach(card => { + const cardCategory = card.getAttribute('data-category') || ''; + + if (selectedCategory === 'all' || cardCategory === selectedCategory) { + card.style.display = 'flex'; + } else { + card.style.display = 'none'; + } + }); + }); +} + +// ist das DOM bereits vollständig aufgebaut? +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initFilter); +} else { + initSorter(); +} \ No newline at end of file -- 2.47.3 From 48f2e90e4924043dcc999d951a698d74295635a4 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:21:58 +0200 Subject: [PATCH 09/22] Debugging 3 --- js/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/filter.js b/js/filter.js index ba972a0..32ec154 100644 --- a/js/filter.js +++ b/js/filter.js @@ -25,5 +25,5 @@ function initFilter() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initFilter); } else { - initSorter(); + initFilter(); } \ No newline at end of file -- 2.47.3 From d9eca0619738081999a2522895e5c52fc933c953 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:30:11 +0200 Subject: [PATCH 10/22] Debugging 4 --- js/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/filter.js b/js/filter.js index 32ec154..f5835e8 100644 --- a/js/filter.js +++ b/js/filter.js @@ -6,7 +6,7 @@ function initFilter() { if (!filterSelect || !listContainer) return; filterSelect.addEventListener('change', function() { - const selectedCategory = this.value; + const selectedCategory = this.value.toLowerCase(); const cards = listContainer.querySelectorAll('.s-res-item'); cards.forEach(card => { -- 2.47.3 From 969adc48363e6b45d7767e77274113e30cdba38f Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:33:09 +0200 Subject: [PATCH 11/22] Debugging 5 --- content/search-results.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/search-results.php b/content/search-results.php index c16c094..24fc443 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -135,9 +135,9 @@ $resultCount = count($results);

Von:

-

📁

+

Kategorie:

- 👍 Likes + Likes

-- 2.47.3 From 520298bae47230e7ccc086c2703ed81f7bb1dd4f Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:35:02 +0200 Subject: [PATCH 12/22] Debugging 6 --- js/filter.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/js/filter.js b/js/filter.js index f5835e8..93257c6 100644 --- a/js/filter.js +++ b/js/filter.js @@ -1,4 +1,3 @@ - function initFilter() { const filterSelect = document.getElementById('category-filter'); const listContainer = document.querySelector('.s-res-list'); @@ -6,22 +5,29 @@ function initFilter() { if (!filterSelect || !listContainer) return; filterSelect.addEventListener('change', function() { - const selectedCategory = this.value.toLowerCase(); + const selectedCategory = this.value.toLowerCase().trim(); const cards = listContainer.querySelectorAll('.s-res-item'); + console.log("--- FILTER GESTARTET ---"); + console.log("Ausgewählt im Dropdown (kleingeschrieben): '" + selectedCategory + "'"); + cards.forEach(card => { - const cardCategory = card.getAttribute('data-category') || ''; + const cardCategory = (card.getAttribute('data-category') || '').toLowerCase().trim(); + const articleTitle = card.querySelector('.s-res-link')?.textContent.trim() || 'Unbekannt'; + + console.log(`Prüfe Artikel "${articleTitle}": Karte hat '${cardCategory}' vs Dropdown '${selectedCategory}'`); if (selectedCategory === 'all' || cardCategory === selectedCategory) { - card.style.display = 'flex'; + card.style.style.display = 'flex'; + console.log("-> TREFFER (wird angezeigt)"); } else { card.style.display = 'none'; + console.log("-> KEIN TREFFER (wird ausgeblendet)"); } }); }); } -// ist das DOM bereits vollständig aufgebaut? if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initFilter); } else { -- 2.47.3 From 5c0e978c9318aeb3f7bbd45f1e7f5d12076ca89e Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:38:00 +0200 Subject: [PATCH 13/22] Debugging 7 --- js/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/filter.js b/js/filter.js index 93257c6..1b78f3b 100644 --- a/js/filter.js +++ b/js/filter.js @@ -18,7 +18,7 @@ function initFilter() { console.log(`Prüfe Artikel "${articleTitle}": Karte hat '${cardCategory}' vs Dropdown '${selectedCategory}'`); if (selectedCategory === 'all' || cardCategory === selectedCategory) { - card.style.style.display = 'flex'; + card.style.display = 'flex'; console.log("-> TREFFER (wird angezeigt)"); } else { card.style.display = 'none'; -- 2.47.3 From 0805958033341445533ef9add450b10a4716a3b2 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:47:33 +0200 Subject: [PATCH 14/22] Anpassung der filter.js es gab ein problem bei der sortierung, da nur 10 ergebnisse angezeigt werden. mit dieser anpassung koennen ergebnisse 'nachruecken' --- js/filter.js | 112 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 16 deletions(-) diff --git a/js/filter.js b/js/filter.js index 1b78f3b..76367c0 100644 --- a/js/filter.js +++ b/js/filter.js @@ -1,35 +1,115 @@ + +let currentClientPage = 1; +const itemsPerPage = 10; + function initFilter() { const filterSelect = document.getElementById('category-filter'); const listContainer = document.querySelector('.s-res-list'); if (!filterSelect || !listContainer) return; + updateVisibility(); + filterSelect.addEventListener('change', function() { - const selectedCategory = this.value.toLowerCase().trim(); - const cards = listContainer.querySelectorAll('.s-res-item'); + currentClientPage = 1; // Bei Filterwechsel zurück auf Seite 1 + updateVisibility(); + }); - console.log("--- FILTER GESTARTET ---"); - console.log("Ausgewählt im Dropdown (kleingeschrieben): '" + selectedCategory + "'"); - cards.forEach(card => { - const cardCategory = (card.getAttribute('data-category') || '').toLowerCase().trim(); - const articleTitle = card.querySelector('.s-res-link')?.textContent.trim() || 'Unbekannt'; + const pageButtons = document.querySelectorAll('.s-res-page-navigation .s-res-page-btn'); + pageButtons.forEach(button => { + button.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); - console.log(`Prüfe Artikel "${articleTitle}": Karte hat '${cardCategory}' vs Dropdown '${selectedCategory}'`); - - if (selectedCategory === 'all' || cardCategory === selectedCategory) { - card.style.display = 'flex'; - console.log("-> TREFFER (wird angezeigt)"); - } else { - card.style.display = 'none'; - console.log("-> KEIN TREFFER (wird ausgeblendet)"); + const targetPage = this.getAttribute('data-page'); + if (targetPage) { + currentClientPage = parseInt(targetPage, 10); + updateVisibility(); } - }); + }, true); }); } +function updateVisibility() { + const filterSelect = document.getElementById('category-filter'); + const listContainer = document.querySelector('.s-res-list'); + const selectedCategory = filterSelect.value.toLowerCase().trim(); + const cards = listContainer.querySelectorAll('.s-res-item'); + + // filtern + let visibleCards = []; + cards.forEach(card => { + const cardCategory = (card.getAttribute('data-category') || '').toLowerCase().trim(); + + if (selectedCategory === 'all' || cardCategory.includes(selectedCategory) || selectedCategory.includes(cardCategory)) { + visibleCards.push(card); + } else { + card.style.display = 'none'; + } + }); + + // nur 10 karten anzeigen + const totalVisible = visibleCards.length; + const totalPages = Math.max(1, ceil(totalVisible / itemsPerPage)); + + // aktuelle seite validieren + if (currentClientPage < 1) currentClientPage = 1; + if (currentClientPage > totalPages) currentClientPage = totalPages; + + const startOffset = (currentClientPage - 1) * itemsPerPage; + const endOffset = startOffset + itemsPerPage; + + visibleCards.forEach((card, index) => { + if (index >= startOffset && index < endOffset) { + card.style.display = 'flex'; + } else { + card.style.display = 'none'; + } + }); + + // update der seitenzahl + updatePaginatorUI(currentClientPage, totalPages); +} + +function updatePaginatorUI(currentPage, totalPages) { + const pageButtons = document.querySelectorAll('.s-res-page-navigation .s-res-page-btn'); + + pageButtons.forEach(button => { + const targetPage = button.getAttribute('data-page'); + + if (button.textContent.includes('«')) { + button.setAttribute('data-page', currentPage - 1); + button.disabled = (currentPage <= 1); + } else if (button.textContent.includes('»')) { + button.setAttribute('data-page', currentPage + 1); + button.disabled = (currentPage >= totalPages); + } else { + const pageNum = parseInt(button.textContent.trim(), 10); + + // Falls es die Seite im neuen Filter gar nicht mehr gibt -> Button verstecken + if (pageNum > totalPages) { + button.style.display = 'none'; + } else { + button.style.display = 'inline-block'; + if (pageNum === currentPage) { + button.classList.add('s-res-page-btn-active'); + } else { + button.classList.remove('s-res-page-btn-active'); + } + } + } + }); +} + +// Hilfsfunktion für Math.ceil in JS +function ceil(val) { return Math.ceil(val); } + +// ist das DOM bereits vollständig aufgebaut? if (document.readyState === 'loading') { + // Falls noch geladen wird, auf das Event warten document.addEventListener('DOMContentLoaded', initFilter); } else { + // Falls das HTML bereits komplett da ist, sofort ausführen initFilter(); } \ No newline at end of file -- 2.47.3 From b3c74c2172a7b92c991f9213132bc131a28ce7c1 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 19:54:31 +0200 Subject: [PATCH 15/22] Debugging 8 --- content/search-results.php | 22 ++++-------- js/filter.js | 72 +++++++++++++++----------------------- 2 files changed, 36 insertions(+), 58 deletions(-) diff --git a/content/search-results.php b/content/search-results.php index 24fc443..02eb2d6 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -27,7 +27,8 @@ if ($currentPage < 1) { $offset = ($currentPage - 1) * $limit; // Nur die Ergebnisse für die aktuelle Seite ausschneiden -$results = array_slice($all_results, $offset, $limit); +//$results = array_slice($all_results, $offset, $limit); +$results = $all_results; $resultCount = count($results); ?> @@ -171,20 +172,11 @@ $resultCount = count($results);
- - - - - - + + + + +
diff --git a/js/filter.js b/js/filter.js index 76367c0..a20f242 100644 --- a/js/filter.js +++ b/js/filter.js @@ -1,4 +1,3 @@ - let currentClientPage = 1; const itemsPerPage = 10; @@ -11,24 +10,25 @@ function initFilter() { updateVisibility(); filterSelect.addEventListener('change', function() { - currentClientPage = 1; // Bei Filterwechsel zurück auf Seite 1 + currentClientPage = 1; updateVisibility(); }); + const navigationContainer = document.querySelector('.s-res-page-navigation'); + if (navigationContainer) { + navigationContainer.addEventListener('click', function(e) { + const button = e.target.closest('.s-res-page-btn'); + if (!button || button.disabled) return; - const pageButtons = document.querySelectorAll('.s-res-page-navigation .s-res-page-btn'); - pageButtons.forEach(button => { - button.addEventListener('click', function(e) { e.preventDefault(); - e.stopPropagation(); - - const targetPage = this.getAttribute('data-page'); + + const targetPage = button.getAttribute('data-page'); if (targetPage) { currentClientPage = parseInt(targetPage, 10); updateVisibility(); } - }, true); - }); + }); + } } function updateVisibility() { @@ -37,11 +37,9 @@ function updateVisibility() { const selectedCategory = filterSelect.value.toLowerCase().trim(); const cards = listContainer.querySelectorAll('.s-res-item'); - // filtern let visibleCards = []; cards.forEach(card => { const cardCategory = (card.getAttribute('data-category') || '').toLowerCase().trim(); - if (selectedCategory === 'all' || cardCategory.includes(selectedCategory) || selectedCategory.includes(cardCategory)) { visibleCards.push(card); } else { @@ -49,11 +47,9 @@ function updateVisibility() { } }); - // nur 10 karten anzeigen const totalVisible = visibleCards.length; - const totalPages = Math.max(1, ceil(totalVisible / itemsPerPage)); + const totalPages = Math.max(1, Math.ceil(totalVisible / itemsPerPage)); - // aktuelle seite validieren if (currentClientPage < 1) currentClientPage = 1; if (currentClientPage > totalPages) currentClientPage = totalPages; @@ -68,38 +64,28 @@ function updateVisibility() { } }); - // update der seitenzahl updatePaginatorUI(currentClientPage, totalPages); } function updatePaginatorUI(currentPage, totalPages) { - const pageButtons = document.querySelectorAll('.s-res-page-navigation .s-res-page-btn'); - - pageButtons.forEach(button => { - const targetPage = button.getAttribute('data-page'); - - if (button.textContent.includes('«')) { - button.setAttribute('data-page', currentPage - 1); - button.disabled = (currentPage <= 1); - } else if (button.textContent.includes('»')) { - button.setAttribute('data-page', currentPage + 1); - button.disabled = (currentPage >= totalPages); - } else { - const pageNum = parseInt(button.textContent.trim(), 10); - - // Falls es die Seite im neuen Filter gar nicht mehr gibt -> Button verstecken - if (pageNum > totalPages) { - button.style.display = 'none'; - } else { - button.style.display = 'inline-block'; - if (pageNum === currentPage) { - button.classList.add('s-res-page-btn-active'); - } else { - button.classList.remove('s-res-page-btn-active'); - } - } - } - }); + const prevBtn = document.getElementById('prev-page-btn'); + const nextBtn = document.getElementById('next-page-btn'); + const numbersContainer = document.getElementById('dynamic-page-numbers'); + + if (!prevBtn || !nextBtn || !numbersContainer) return; + + prevBtn.setAttribute('data-page', currentPage - 1); + prevBtn.disabled = (currentPage <= 1); + + nextBtn.setAttribute('data-page', currentPage + 1); + nextBtn.disabled = (currentPage >= totalPages); + + let buttonsHTML = ''; + for (let i = 1; i <= totalPages; i++) { + const activeClass = (i === currentPage) ? 's-res-page-btn-active' : ''; + buttonsHTML += ` `; + } + numbersContainer.innerHTML = buttonsHTML; } // Hilfsfunktion für Math.ceil in JS -- 2.47.3 From 1e6ac0fcd9c7f3af86f01c22cd09aa6c86e24d14 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 20:14:07 +0200 Subject: [PATCH 16/22] Implementierung des validators --- content/search-results.php | 18 ++++--- php/validator/search-validator.php | 75 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 php/validator/search-validator.php diff --git a/content/search-results.php b/content/search-results.php index 02eb2d6..e742c03 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -1,4 +1,7 @@ +

Kategorie filtern

diff --git a/php/validator/search-validator.php b/php/validator/search-validator.php deleted file mode 100644 index 0f789cf..0000000 --- a/php/validator/search-validator.php +++ /dev/null @@ -1,75 +0,0 @@ -= 5 && $zeichenAnzahl <= 12; -} - -/** - * Prüft ein optionales Passwort. - * - * Ein leeres Passwort ist erlaubt und bedeutet, - * dass das bestehende Passwort unverändert bleibt. - * Falls ein Passwort angegeben wurde, wird es - * mit den normalen Passwortregeln geprüft. - * - * @param string|null $password Zu prüfendes Passwort - * - * @return bool true wenn das Passwort gültig oder leer ist, - * sonst false - */ -function userOptionalPasswordValidator($password) -{ - if (!isset($password) || $password === '') { - return true; - } - - return userPasswordValidator($password); -} \ No newline at end of file -- 2.47.3 From 87e7be1c10681b68f3f554c0c349753550e7f835 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 20:30:45 +0200 Subject: [PATCH 18/22] Implementation des validators und korrektes einbinden --- php/controller/search-results-controller.php | 7 +++- php/validator/search-validator.php | 40 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 php/validator/search-validator.php diff --git a/php/controller/search-results-controller.php b/php/controller/search-results-controller.php index 4d2a654..a78a23b 100644 --- a/php/controller/search-results-controller.php +++ b/php/controller/search-results-controller.php @@ -5,11 +5,12 @@ if (session_status() === PHP_SESSION_NONE) { require_once '../model/LocalArticleManager.php'; require_once '../model/ArticleManager.php'; require_once '../model/Article.php'; +require_once '../validator/search-validator.php'; if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) { $search = trim($_GET["q"]); - if (empty($search)) { + if (!searchQueryValidator($search)) { $_SESSION["search_results"] = []; $_SESSION["search_query"] = ""; $_SESSION["message"] = "missing_parameters"; @@ -51,6 +52,7 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) { "category" => $obj->category, "tags" => $obj->tags, "creationDate" => $obj->creationDate + //"likes" => $obj->likes ]; } @@ -65,6 +67,9 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) { $sort = $_GET['sort'] ?? 'alphabet'; $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10; + if (!searchLimitValidator($limit)) { + $limit = 10; + } $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; header("Location: ../../index.php?pfad=search-results&q=" . urlencode($search) . "&sort=" . urlencode($sort) . "&limit=" . $limit . "&page=" . $page); exit(); diff --git a/php/validator/search-validator.php b/php/validator/search-validator.php new file mode 100644 index 0000000..8c0ea63 --- /dev/null +++ b/php/validator/search-validator.php @@ -0,0 +1,40 @@ + 50) { + return false; + } + + // Erlaubt Buchstaben (inkl. Umlaut/ß), Zahlen, Leerzeichen sowie ?, !, ., -, _ + $searchPattern = '/^[a-zA-Z0-9äöüÄÖÜß\s?!.,\-_]+$/u'; + + return preg_match($searchPattern, $query) === 1; +} + +/** + * Prüft, ob das gewählte Treffer-Limit erlaubt ist. + * + * @param int|string $limit Das zu prüfende Limit + * + * @return bool true wenn das Limit 10, 20, 50 oder 100 ist, sonst false + */ +function searchLimitValidator($limit) +{ + $allowedLimits = [10, 20, 50, 100]; + return in_array((int)$limit, $allowedLimits, true); +} \ No newline at end of file -- 2.47.3 From 057639be7ca792075f7ae53c78d9d305e12dc4c8 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 20:38:19 +0200 Subject: [PATCH 19/22] anpassung der error message --- content/search-results.php | 4 ++-- php/controller/search-results-controller.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/search-results.php b/content/search-results.php index 02eb2d6..9b8f28b 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -149,8 +149,8 @@ $resultCount = count($results); elseif (isset($_SESSION["search_query"]) && $_SESSION["search_query"] !== "" && $resultCount === 0): ?>

Keine Beiträge zu diesem Suchbegriff gefunden.

-

Bitte überprüfe deine Sucheingabe und versuche es erneut!

+ elseif (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_search_query"): ?> +

Unzulässige Suchanfrage

Date: Wed, 17 Jun 2026 20:42:31 +0200 Subject: [PATCH 20/22] aufraeumen 2 --- content/search-results.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/content/search-results.php b/content/search-results.php index 9b8f28b..85ed579 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -137,9 +137,6 @@ $resultCount = count($results);

Von:

Kategorie:

-

- Likes -

-- 2.47.3 From 395041fd444cda0832fa035098a6b8aa061a4c08 Mon Sep 17 00:00:00 2001 From: rirat-0 Date: Wed, 17 Jun 2026 21:59:12 +0200 Subject: [PATCH 21/22] anpassung, damit die like sortierung client seitig ist --- content/search-results.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/search-results.php b/content/search-results.php index 25b96be..3b558b6 100644 --- a/content/search-results.php +++ b/content/search-results.php @@ -64,7 +64,7 @@ $resultCount = count($results); Alphabetisch