Compare commits

..

57 Commits

Author SHA1 Message Date
niklas.ortmann 9fcb09ee05 Merge branch 'dev' into SuchergebnisseJS 2026-07-01 19:13:31 +02:00
niklas.ortmann e9a92eaec7 merge 2026-07-01 19:11:32 +02:00
niklas.ortmann 54209929fe Merge pull request 'Implementierung von Nutzungsbedingungen und Barrierefreiheitserklärung' (#46) from Nutzungsbedingungen&barrierefreiheit into dev
Reviewed-on: #46
Reviewed-by: niklas.ortmann <ortmann.niklas@yahoo.de>
2026-06-30 22:41:56 +02:00
rirat-0 5321534d71 anpassung der filterJS fuer ergebnislimits 2026-06-30 21:13:02 +02:00
caroline.slt 7cf542c270 Richtig Einbinden 2026-06-30 21:09:49 +02:00
caroline.slt c45d23a6b3 Korrektur 2026-06-30 21:07:53 +02:00
caroline.slt ab094429b7 Erste Implementierung 2026-06-30 21:04:24 +02:00
niklas.ortmann 15ea5ddbfb Merge pull request 'datenschutz und impressum wurden erstellt, die readme entsprechend angepasst' (#43) from ImpressumUndDatenschutz into dev
Reviewed-on: #43
Reviewed-by: niklas.ortmann <ortmann.niklas@yahoo.de>
2026-06-30 18:13:51 +02:00
rirat-0 cbb662d159 testing 2026-06-30 16:16:14 +02:00
rirat-0 c5e7439541 rechtschreibung 2026-06-30 16:11:08 +02:00
rirat-0 a748d94305 datenschutz und impressum wurden ersten, die readme entsprechend angepasst 2026-06-30 16:08:26 +02:00
NOrtmann1 d5a3f4298f Update README.md 2026-06-17 22:39:43 +02:00
NOrtmann1 b4a5c9c020 Update README.md 2026-06-17 22:38:23 +02:00
NOrtmann1 6d6f1f31b0 Update updateArticle-controller.php 2026-06-17 22:28:01 +02:00
NOrtmann1 cafffba921 Update updateArticle-controller.php 2026-06-17 22:21:00 +02:00
NOrtmann1 8fa758d825 Update dataSources.local.xml 2026-06-17 22:20:55 +02:00
rirat-0 17034c928b anpassung der search results fuer die filterung nach likes 2026-06-17 22:03:51 +02:00
rirat-0 395041fd44 anpassung, damit die like sortierung client seitig ist 2026-06-17 21:59:12 +02:00
rirat-0 c117c7e641 Merge branch 'dev' into SuchergebnisseJS 2026-06-17 21:48:58 +02:00
niklas.ortmann 01fc8537cf Merge pull request 'Bewertungssystem' (#36) from Bewertungssystem into dev
Reviewed-on: #36
2026-06-17 21:29:24 +02:00
rirat-0 12ad9759cf aufraeumen 2 2026-06-17 20:42:31 +02:00
rirat-0 057639be7c anpassung der error message 2026-06-17 20:38:19 +02:00
rirat-0 87e7be1c10 Implementation des validators und korrektes einbinden 2026-06-17 20:30:45 +02:00
rirat-0 1db77346d0 Revert "Implementierung des validators"
This reverts commit 1e6ac0fcd9.
2026-06-17 20:14:59 +02:00
rirat-0 1e6ac0fcd9 Implementierung des validators 2026-06-17 20:14:07 +02:00
rirat-0 b3c74c2172 Debugging 8 2026-06-17 19:54:31 +02:00
rirat-0 0805958033 Anpassung der filter.js
es gab ein problem bei der sortierung, da nur 10 ergebnisse angezeigt werden. mit dieser anpassung koennen ergebnisse 'nachruecken'
2026-06-17 19:47:33 +02:00
rirat-0 5c0e978c93 Debugging 7 2026-06-17 19:38:00 +02:00
rirat-0 520298bae4 Debugging 6 2026-06-17 19:35:02 +02:00
rirat-0 969adc4836 Debugging 5 2026-06-17 19:33:09 +02:00
rirat-0 d9eca06197 Debugging 4 2026-06-17 19:30:11 +02:00
rirat-0 48f2e90e49 Debugging 3 2026-06-17 19:21:58 +02:00
rirat-0 358247a2a1 Filter funktion implementiert 2026-06-17 19:19:31 +02:00
rirat-0 de4d2fe5c0 sortieren nach likes nun moeglich 2026-06-17 17:58:40 +02:00
rirat-0 e1102eb7db Merge branch 'dev' into SuchergebnisseJS 2026-06-17 17:48:08 +02:00
rirat-0 a8df9590fd updater sorter.js fuer client seite 2026-06-17 15:38:59 +02:00
rirat-0 3e453e22ec verschieben der script aufrufe in die index.php 2026-06-17 15:31:05 +02:00
rirat-0 beeab0ec90 aufraeumen 2026-06-17 14:50:05 +02:00
rirat-0 9353a7eaaa Debugging 2 2026-06-17 14:48:33 +02:00
rirat-0 cac8f3046d Debugging 1 2026-06-17 14:45:01 +02:00
rirat-0 66eeac372c Anpassung der results seite und erstellung der script-datei 2026-06-17 14:41:05 +02:00
niklas.ortmann 4a6a3cf708 search-results -> Likes-Sortierung 2026-06-17 13:23:39 +02:00
niklas.ortmann cb44c3f5a8 Update search-results-controller.php 2026-06-17 13:11:03 +02:00
niklas.ortmann eb0a34e959 Create like-controller.php 2026-06-17 13:00:12 +02:00
niklas.ortmann 8d683cc1a0 showArticle -> like-Button 2026-06-17 12:53:33 +02:00
niklas.ortmann ee41dd4cfe Update LocalArticleManager.php 2026-06-17 12:35:17 +02:00
niklas.ortmann 3d50e6e3b3 ArticleManager um LIKES erweitert...
Einführung einer dynamischen Like-Funktion für Beiträge
2026-06-17 12:26:18 +02:00
niklas.ortmann 4f8d11881d Merge pull request 'home zeigt nun dynamisch die Kategorien an' (#35) from Startseite into dev
Reviewed-on: #35
2026-06-17 12:08:10 +02:00
niklas.ortmann 5c924d3277 Update profile.php 2026-06-17 12:06:49 +02:00
niklas.ortmann 7f703a8386 Update profile.php 2026-06-17 12:03:25 +02:00
niklas.ortmann f063ea4741 Update profile.php 2026-06-17 12:02:41 +02:00
niklas.ortmann ca643dd298 Update profile.php 2026-06-17 12:02:20 +02:00
niklas.ortmann e19225e49e Update profile.php 2026-06-17 12:01:38 +02:00
niklas.ortmann 9ffbca679e Update profile.php 2026-06-17 12:00:59 +02:00
niklas.ortmann d90a10e462 Update profile.php 2026-06-17 12:00:36 +02:00
niklas.ortmann b6f25d041b Anmerkungen von Maximilian zu Blatt 04
Ist der PHP-Code gut verständlich und übersichtlich strukturiert? Beinahe vollständig. ArticleManager::getInstance() erzeugt Dummy-Daten direkt beim Abruf der Instanz; etwas unübersichtlich, aber im Kontext der Dummy-Daten nicht zu gewichten (heißt es wird nicht negativ angerechnet).

Wie ist die Code-Qualität? Teilweise. articleAuthorValidator() ist mit TODO versehen und gibt immer true zurück. In home.php wird $dummy3->getId() aufgerufen, bevor geprüft wird, ob $dummy3 überhaupt existiert.

Wird redundanter Code vermieden? Teilweise. Die Kategorie-Listen stehen mehrfach separat in createArticle.php, updateArticle.php, navbar.php und article-validator.php. Fehlerausgaben für Artikel-Erstellung und Artikel-Bearbeitung sind ebenfalls fast identisch doppelt vorhanden.

Findet eine adäquate Trennung von PHP-Code und HTML-Code statt? Beinahe vollständig. In den Views werden teilweise noch Controller direkt eingebunden, z. B. profile.php bindet profile-controller.php ein und updateArticle.php nutzt showArticle-controller.php, um Daten zu laden (kann man theoretisch so machen, an dieser Stelle ist die Trennung dann aber nicht mehr wirklich sauber auf einem fundamentalen Level).

Ist eine Trennung in Model, View und Controller ersichtlich und auch korrekt durchgeführt? Teilweise. content-Dateien übernehmen teilweise Routing-/Controller-Aufgaben. Views binden Controller selbst ein.

Wurde das DAO-Pattern korrekt umgesetzt? Ok hier und da mit extends ein wenig fragwürdig aber kein Fehler.

Werden Sessions korrekt eingesetzt? Ok. session_start() wird mehrfach aufgerufen, z. B. in index.php, navbar.php, home-controller.php und search-results.php. Nach Login oder Registrierung wird keine neue Session-ID erzeugt (dies ist kein muss).

Findet in jedem Skript falls notwendig zunächst eine Authentifizierung angemeldeter Nutzer statt? Teilweise. createArticle-controller.php prüft bei direktem Aufruf nicht, ob ein Nutzer angemeldet ist. updateArticle-controller.php prüft ebenfalls nicht zu Beginn sauber die Authentifizierung. Die Authentifizierung liegt teilweise nur in der Formularseite, nicht im verarbeitenden Controller.

Ist bei „Eintrag bearbeiten/löschen“ sichergestellt, dass nur der Eigentümer den Eintrag verändern kann? Beinahe vollständig. Die eigentliche Aktualisierung prüft den Autor. Das Bearbeitungsformular updateArticle.php kann aber von jedem eingeloggten Nutzer mit fremder Artikel-ID geöffnet werden.

Werden in jedem Skript alle GET- bzw. POST-Parameter überprüft und ggf. auf Validität überprüft? Nein. pfad in index.php wird nicht per Whitelist validiert. id in showArticle-controller.php und updateArticle-controller.php wird nicht als gültige numerische ID geprüft. sort in search-results-controller.php wird nicht gegen erlaubte Werte validiert.

Werden bei Eingabefehlern durch den Nutzer die von ihm eingegebenen Daten bei der erneuten Formularanzeige übernommen? Teilweise. In createArticle.php wird old_category zwar gespeichert, aber nicht wieder im Dropdown ausgewählt (in updateArticle.php als TODO vermerkt). Beim Login wird die eingegebene E-Mail-Adresse nach einem Fehler nicht erneut angezeigt.

Wird eine adäquate Überprüfung von potentiellen Fehlern und Fehlerbehandlung durchgeführt? Teilweise. LocalArticleManager::saveArticle() prüft nicht, ob file_put_contents() erfolgreich war. getAllArticles() ignoriert fehlerhaftes JSON und gibt dann einfach ein leeres Array zurück. home-controller.php gibt im Fehlerfall direkt die Exception-Message aus.

Erfolgen bei Aktionen des Nutzers sowohl positive als auch negative Meldungen auf der nachfolgend angezeigten Seite? Teilweise. Nach erfolgreichem Login oder Registrierung gibt es keine sichtbare Erfolgsmeldung. Nach erfolgreicher Profiländerung gibt es ebenfalls keine positive Meldung. Beim Löschen des Accounts erfolgt nur eine Weiterleitung ohne Feedback.

Werden alle Eingabedaten beim Einbau in die HTML-Seite mit htmlspecialchars bzw. htmlentities HTML-kodiert? Beinahe vollständig. In home.php werden die Titel der Dummy-/Artikel-Links ohne htmlspecialchars() ausgegeben.

Werden alle Eingabedaten beim Einbau in eine URL mit urlencode URL-kodiert? Ok. Artikel-IDs werden mehrfach direkt in URLs eingesetzt, z. B. in profile.php, search-results.php und showCategory.php (Anmerkung: Rechne ich hier nicht negativ an). Auch die Formular-Action in updateArticle.php baut die ID ohne urlencode() in die URL ein (rechne ich ebenfalls nicht negativ an).
2026-06-17 11:58:49 +02:00
niklas.ortmann d1b3641754 Update dataSources.local.xml 2026-06-17 11:11:19 +02:00
29 changed files with 976 additions and 136 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
<component name="dataSourceStorageLocal" created-in="IU-253.32098.101">
<data-source name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
<database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51">
<identifier-quote-string>&quot;</identifier-quote-string>
+14 -8
View File
@@ -16,16 +16,22 @@
## Bekannte Fehler und Mängel
- Bitte auf die gesetzten TODO's achten. Wenn Inhalte fehlen, sind sie i.d.R. als TODO kommentiert.
- Die Suchseite und Kategorieseite packen momentan alle passenden Beiträge untereinander. Später sollen zunächst 10
Ergebnisse auf einer Seite angezeigt werden.
- Die Kategorieseite listet momentan alle passenden Beiträge untereinander. Später sollen mit einem Paginator die neusten
Beiträge nacheinander aufgelistet werden (ähnlich wie bei der Suche, wenn nach Fach gefiltert wird).
- Wenn ein Bild aus einem Beitrag entfernt wird, dann wird noch nicht die Datei im Pfad /uploads gelöscht.
- id in showArticle-controller.php und updateArticle-controller.php wird nicht als gültige numerische ID geprüft.
- Bilder im Beitragseditor sollen zukünftig eine Bildunterschrift bekommen und größenverstellbar sein.
- Die Elemente eines Contents im Beitrag werden momentan stumpf untereinander aufgelistet. Soll später
sich responisve auch nebeneinander orientieren usw.
## Besonderheiten des Projektes
- Es wurde ein einfacher Beitrags-Editor erstellt. Mit diesem können Beiträge erstellt oder bearbeitet werden.
Es handelt es sich um eine einfache Version. Später sollen z.B. Bilder und die Positionierung der Elemente folgen.
- Es sind drei Dummy-Beiträge r den Nutzer max.mustermann hinterlegt.
- Die Such-Seite umfasst eine Such- und Sortierfunktion. Jedoch fehlt noch eine
Filterfunktion (z.B. nur Mathe anzeigen).
- Es wurde AJAX verwendet, um asynchrone Erstellung von Kommentaren zu implementieren. Es ermöglicht dem Nutzer, einen
Kommentar abzusenden, ohne dass die gesamte Webseite neu geladen werden muss.
- Mit JavaScript werden auch clientseitig die Kommentare visuell hinzugefügt und die Kommentarbäume aufgebaut.
- JavaScript wird verwendet, um im erweitertem Beitragseditor clientseitig einzelne Content-Boxen erstellen und löschen
zu können.
- JavaScript wird ebenfalls verwendet, um in die Suchergebnisse clientseitig zu sortieren.
## Sonstiges
- Das Datenschema befindet sich unter /planung/Datenschema.pdf
- Das Datenschema befindet sich unter /planung/Datenschema.pdf
- Im Impressum, sowie der Datenschutzerklaerung wurden Dummy-Daten verwendet
+66
View File
@@ -0,0 +1,66 @@
<!--
Seite: Barrierefreiheitserklärung
Inhalt: Erklärung zur Barrierefreiheit der Plattform
-->
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="description" content="EduForge">
<meta name="author" content="Niklas Ortmann">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="../images/logos/logo_icon.ico">
<link rel="stylesheet" href="../css/main.css">
<link rel="stylesheet" href="../css/navbar.css">
<link rel="stylesheet" href="../css/footer.css">
<title>EduForge</title>
</head>
<body>
<?php
include_once 'includes/navbar.php';
?>
<main>
<h1>Barrierefreiheitserklärung</h1>
<p>
Wir bemühen uns, die Plattform EduForge möglichst barrierefrei zugänglich zu gestalten.
Die Website wurde im Rahmen eines Hochschulprojekts erstellt.
</p>
<h2>Stand der Vereinbarkeit</h2>
<p>
Diese Website ist teilweise barrierefrei. Einzelne Funktionen oder Inhalte können
noch nicht vollständig barrierefrei nutzbar sein.
</p>
<h2>Nicht barrierefreie Inhalte</h2>
<p>
Einzelne Bilder verfügen möglicherweise noch nicht über vollständige Alternativtexte.
Außerdem kann die Bedienung mit Tastatur oder Screenreader in einzelnen Bereichen
eingeschränkt sein.
</p>
<h2>Feedback und Kontakt</h2>
<p>
Sollten Ihnen Barrieren auf unserer Website auffallen, kontaktieren Sie uns bitte
über die im Impressum angegebenen Kontaktdaten.
</p>
<h2>Erstellungsdatum</h2>
<p>
Diese Erklärung wurde im Rahmen eines Hochschulprojekts erstellt.
</p>
</main>
<?php
include_once 'includes/footer.php';
?>
</body>
</html>
+152 -10
View File
@@ -4,15 +4,157 @@
-->
<main>
<h1>Datenschutz</h1>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>
<h1>Datenschutz&shy;erkl&auml;rung</h1>
<h2>1. Datenschutz auf einen Blick</h2>
<h3>Allgemeine Hinweise</h3> <p>Die folgenden Hinweise geben einen einfachen &Uuml;berblick dar&uuml;ber,
was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind
alle Daten, mit denen Sie pers&ouml;nlich identifiziert werden k&ouml;nnen. Ausf&uuml;hrliche Informationen zum
Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgef&uuml;hrten
Datenschutzerkl&auml;rung.</p>
<h3>Datenerfassung auf dieser Website</h3> <h4>Wer ist verantwortlich f&uuml;r die Datenerfassung auf dieser
Website?</h4> <p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen
Kontaktdaten k&ouml;nnen Sie dem Abschnitt &bdquo;Hinweis zur verantwortlichen Stelle&ldquo; in dieser
Datenschutzerkl&auml;rung entnehmen.</p> <h4>Wie erfassen wir Ihre Daten?</h4> <p>Ihre Daten werden zum
einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z.&nbsp;B. um Daten handeln, die Sie in ein
Kontaktformular eingeben.</p> <p>Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch
der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.&nbsp;B. Internetbrowser,
Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese
Website betreten.</p> <h4>Wof&uuml;r nutzen wir Ihre Daten?</h4> <p>Ein Teil der Daten wird erhoben, um eine
fehlerfreie Bereitstellung der Website zu gew&auml;hrleisten. Andere Daten k&ouml;nnen zur Analyse Ihres
Nutzerverhaltens verwendet werden. Sofern &uuml;ber die Website Vertr&auml;ge geschlossen oder angebahnt
werden k&ouml;nnen, werden die &uuml;bermittelten Daten auch f&uuml;r Vertragsangebote, Bestellungen oder
sonstige Auftragsanfragen verarbeitet.</p> <h4>Welche Rechte haben Sie bez&uuml;glich Ihrer Daten?</h4>
<p>Sie haben jederzeit das Recht, unentgeltlich Auskunft &uuml;ber Herkunft, Empf&auml;nger und Zweck Ihrer
gespeicherten personenbezogenen Daten zu erhalten. Sie haben au&szlig;erdem ein Recht, die Berichtigung oder
L&ouml;schung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben,
k&ouml;nnen Sie diese Einwilligung jederzeit f&uuml;r die Zukunft widerrufen. Au&szlig;erdem haben Sie das Recht,
unter bestimmten Umst&auml;nden die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu
verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zust&auml;ndigen Aufsichtsbeh&ouml;rde
zu.</p> <p>Hierzu sowie zu weiteren Fragen zum Thema Datenschutz k&ouml;nnen Sie sich jederzeit an uns
wenden.</p>
<h2>2. Hosting</h2>
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:</p>
<h3>Externes Hosting</h3> <p>Diese Website wird extern gehostet. Die personenbezogenen Daten, die auf dieser
Website erfasst werden, werden auf den Servern des Hosters / der Hoster gespeichert. Hierbei kann es sich
v.&nbsp;a. um IP-Adressen, Kontaktanfragen, Meta- und Kommunikationsdaten, Vertragsdaten, Kontaktdaten,
Namen, Websitezugriffe und sonstige Daten, die &uuml;ber eine Website generiert werden, handeln.</p> <p>Das
externe Hosting erfolgt zum Zwecke der Vertragserf&uuml;llung gegen&uuml;ber unseren potenziellen und
bestehenden Kunden (Art. 6 Abs. 1 lit. b DSGVO) und im Interesse einer sicheren, schnellen und effizienten
Bereitstellung unseres Online-Angebots durch einen professionellen Anbieter (Art. 6 Abs. 1 lit. f DSGVO). Sofern
eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschlie&szlig;lich auf Grundlage von
Art. 6 Abs. 1 lit. a DSGVO und &sect; 25 Abs. 1 TDDDG, soweit die Einwilligung die Speicherung von Cookies oder
den Zugriff auf Informationen im Endger&auml;t des Nutzers (z.&nbsp;B. Device-Fingerprinting) im Sinne des
TDDDG umfasst. Die Einwilligung ist jederzeit widerrufbar.</p> <p>Unser(e) Hoster wird bzw. werden Ihre Daten
nur insoweit verarbeiten, wie dies zur Erf&uuml;llung seiner Leistungspflichten erforderlich ist und unsere
Weisungen in Bezug auf diese Daten befolgen.</p> <p>Wir setzen folgende(n) Hoster ein:</p>
<p>-Platzhalter-</p>
<h4>Auftragsverarbeitung</h4> <p>Wir haben einen Vertrag &uuml;ber Auftragsverarbeitung (AVV) zur Nutzung
des oben genannten Dienstes geschlossen. Hierbei handelt es sich um einen datenschutzrechtlich vorgeschriebenen
Vertrag, der gew&auml;hrleistet, dass dieser die personenbezogenen Daten unserer Websitebesucher nur nach
unseren Weisungen und unter Einhaltung der DSGVO verarbeitet.</p>
<h2>3. Allgemeine Hinweise und Pflicht&shy;informationen</h2>
<h3>Datenschutz</h3> <p>Die Betreiber dieser Seiten nehmen den Schutz Ihrer pers&ouml;nlichen Daten sehr
ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen
Datenschutzvorschriften sowie dieser Datenschutzerkl&auml;rung.</p> <p>Wenn Sie diese Website benutzen,
werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie
pers&ouml;nlich identifiziert werden k&ouml;nnen. Die vorliegende Datenschutzerkl&auml;rung erl&auml;utert,
welche Daten wir erheben und wof&uuml;r wir sie nutzen. Sie erl&auml;utert auch, wie und zu welchem Zweck das
geschieht.</p> <p>Wir weisen darauf hin, dass die Daten&uuml;bertragung im Internet (z.&nbsp;B. bei der
Kommunikation per E-Mail) Sicherheitsl&uuml;cken aufweisen kann. Ein l&uuml;ckenloser Schutz der Daten vor
dem Zugriff durch Dritte ist nicht m&ouml;glich.</p>
<h3>Hinweis zur verantwortlichen Stelle</h3> <p>Die verantwortliche Stelle f&uuml;r die Datenverarbeitung auf
dieser Website ist:</p> <p>Ammerlaender Heerstrasse, 114-118</p>
<p>Telefon: 0123456789<br />
E-Mail: GruppeC@uol.de</p>
<p>Verantwortliche Stelle ist die nat&uuml;rliche oder juristische Person, die allein oder gemeinsam mit anderen
&uuml;ber die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z.&nbsp;B. Namen, E-Mail-
Adressen o. &Auml;.) entscheidet.</p>
<h3>Speicherdauer</h3> <p>Soweit innerhalb dieser Datenschutzerkl&auml;rung keine speziellere Speicherdauer
genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck f&uuml;r die Datenverarbeitung
entf&auml;llt. Wenn Sie ein berechtigtes L&ouml;schersuchen geltend machen oder eine Einwilligung zur
Datenverarbeitung widerrufen, werden Ihre Daten gel&ouml;scht, sofern wir keine anderen rechtlich
zul&auml;ssigen Gr&uuml;nde f&uuml;r die Speicherung Ihrer personenbezogenen Daten haben (z.&nbsp;B. steuer-
oder handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die L&ouml;schung nach Fortfall
dieser Gr&uuml;nde.</p>
<h3>Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website</h3> <p>Sofern Sie
in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre personenbezogenen Daten auf Grundlage von Art.
6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs. 2 lit. a DSGVO, sofern besondere Datenkategorien nach Art. 9 Abs. 1 DSGVO
verarbeitet werden. Im Falle einer ausdr&uuml;cklichen Einwilligung in die &Uuml;bertragung personenbezogener
Daten in Drittstaaten erfolgt die Datenverarbeitung au&szlig;erdem auf Grundlage von Art. 49 Abs. 1 lit. a DSGVO.
Sofern Sie in die Speicherung von Cookies oder in den Zugriff auf Informationen in Ihr Endger&auml;t (z.&nbsp;B. via
Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zus&auml;tzlich auf Grundlage von &sect;
25 Abs. 1 TDDDG. Die Einwilligung ist jederzeit widerrufbar. Sind Ihre Daten zur Vertragserf&uuml;llung oder zur
Durchf&uuml;hrung vorvertraglicher Ma&szlig;nahmen erforderlich, verarbeiten wir Ihre Daten auf Grundlage des
Art. 6 Abs. 1 lit. b DSGVO. Des Weiteren verarbeiten wir Ihre Daten, sofern diese zur Erf&uuml;llung einer
rechtlichen Verpflichtung erforderlich sind, auf Grundlage von Art. 6 Abs. 1 lit. c DSGVO. Die Datenverarbeitung
kann ferner auf Grundlage unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. &Uuml;ber die
jeweils im Einzelfall einschl&auml;gigen Rechtsgrundlagen wird in den folgenden Abs&auml;tzen dieser
Datenschutzerkl&auml;rung informiert.</p>
<h3>Empf&auml;nger von personenbezogenen Daten</h3> <p>Im Rahmen unserer Gesch&auml;ftst&auml;tigkeit
arbeiten wir mit verschiedenen externen Stellen zusammen. Dabei ist teilweise auch eine &Uuml;bermittlung von
personenbezogenen Daten an diese externen Stellen erforderlich. Wir geben personenbezogene Daten nur dann an
externe Stellen weiter, wenn dies im Rahmen einer Vertragserf&uuml;llung erforderlich ist, wenn wir gesetzlich
hierzu verpflichtet sind (z.&nbsp;B. Weitergabe von Daten an Steuerbeh&ouml;rden), wenn wir ein berechtigtes
Interesse nach Art. 6 Abs. 1 lit. f DSGVO an der Weitergabe haben oder wenn eine sonstige Rechtsgrundlage die
Datenweitergabe erlaubt. Beim Einsatz von Auftragsverarbeitern geben wir personenbezogene Daten unserer
Kunden nur auf Grundlage eines g&uuml;ltigen Vertrags &uuml;ber Auftragsverarbeitung weiter. Im Falle einer
gemeinsamen Verarbeitung wird ein Vertrag &uuml;ber gemeinsame Verarbeitung geschlossen.</p>
<h3>Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3> <p>Viele Datenverarbeitungsvorg&auml;nge sind
nur mit Ihrer ausdr&uuml;cklichen Einwilligung m&ouml;glich. Sie k&ouml;nnen eine bereits erteilte Einwilligung
jederzeit widerrufen. Die Rechtm&auml;&szlig;igkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom
Widerruf unber&uuml;hrt.</p>
<h3>Widerspruchsrecht gegen die Datenerhebung in besonderen F&auml;llen sowie gegen Direktwerbung (Art. 21
DSGVO)</h3> <p>WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F
DSGVO ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GR&Uuml;NDEN, DIE SICH AUS IHRER
BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN
WIDERSPRUCH EINZULEGEN; DIES GILT AUCH F&Uuml;R EIN AUF DIESE BESTIMMUNGEN GEST&Uuml;TZTES
PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN
SIE DIESER DATENSCHUTZERKL&Auml;RUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE
BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR
K&Ouml;NNEN ZWINGENDE SCHUTZW&Uuml;RDIGE GR&Uuml;NDE F&Uuml;R DIE VERARBEITUNG
NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN &Uuml;BERWIEGEN ODER DIE
VERARBEITUNG DIENT DER GELTENDMACHUNG, AUS&Uuml;BUNG ODER VERTEIDIGUNG VON
RECHTSANSPR&Uuml;CHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).</p> <p>WERDEN IHRE
PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO HABEN SIE DAS
RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER PERSONENBEZOGENER
DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH F&Uuml;R DAS PROFILING,
SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN,
WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER
DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).</p>
<h3>Beschwerde&shy;recht bei der zust&auml;ndigen Aufsichts&shy;beh&ouml;rde</h3> <p>Im Falle von
Verst&ouml;&szlig;en gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer
Aufsichtsbeh&ouml;rde, insbesondere in dem Mitgliedstaat ihres gew&ouml;hnlichen Aufenthalts, ihres
Arbeitsplatzes oder des Orts des mutma&szlig;lichen Versto&szlig;es zu. Das Beschwerderecht besteht
unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.</p>
<h3>Recht auf Daten&shy;&uuml;bertrag&shy;barkeit</h3> <p>Sie haben das Recht, Daten, die wir auf Grundlage
Ihrer Einwilligung oder in Erf&uuml;llung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in
einem g&auml;ngigen, maschinenlesbaren Format aush&auml;ndigen zu lassen. Sofern Sie die direkte
&Uuml;bertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch
machbar ist.</p>
<h3>Auskunft, Berichtigung und L&ouml;schung</h3> <p>Sie haben im Rahmen der geltenden gesetzlichen
Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft &uuml;ber Ihre gespeicherten personenbezogenen
Daten, deren Herkunft und Empf&auml;nger und den Zweck der Datenverarbeitung und ggf. ein Recht auf
Berichtigung oder L&ouml;schung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene
Daten k&ouml;nnen Sie sich jederzeit an uns wenden.</p>
<h3>Recht auf Einschr&auml;nkung der Verarbeitung</h3> <p>Sie haben das Recht, die Einschr&auml;nkung der
Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu k&ouml;nnen Sie sich jederzeit an uns wenden.
Das Recht auf Einschr&auml;nkung der Verarbeitung besteht in folgenden F&auml;llen:</p> <ul> <li>Wenn Sie die
Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, ben&ouml;tigen wir in der Regel Zeit,
um dies zu &uuml;berpr&uuml;fen. F&uuml;r die Dauer der Pr&uuml;fung haben Sie das Recht, die
Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn die
Verarbeitung Ihrer personenbezogenen Daten unrechtm&auml;&szlig;ig geschah/geschieht, k&ouml;nnen Sie statt
der L&ouml;schung die Einschr&auml;nkung der Datenverarbeitung verlangen.</li> <li>Wenn wir Ihre
personenbezogenen Daten nicht mehr ben&ouml;tigen, Sie sie jedoch zur Aus&uuml;bung, Verteidigung oder
Geltendmachung von Rechtsanspr&uuml;chen ben&ouml;tigen, haben Sie das Recht, statt der L&ouml;schung die
Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn Sie einen
Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abw&auml;gung zwischen Ihren und unseren
Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen &uuml;berwiegen, haben Sie das
Recht, die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> </ul>
<p>Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschr&auml;nkt haben, d&uuml;rfen diese
Daten &ndash; von ihrer Speicherung abgesehen &ndash; nur mit Ihrer Einwilligung oder zur Geltendmachung,
Aus&uuml;bung oder Verteidigung von Rechtsanspr&uuml;chen oder zum Schutz der Rechte einer anderen
nat&uuml;rlichen oder juristischen Person oder aus Gr&uuml;nden eines wichtigen &ouml;ffentlichen Interesses der
Europ&auml;ischen Union oder eines Mitgliedstaats verarbeitet werden.</p>
<p>Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a></p>
</main>
+7 -9
View File
@@ -6,14 +6,12 @@
<main>
<h1>Impressum</h1>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>
<p>Gruppe C<br />
Ammerlaender Heerstrasse, 114-118<br />
26129 Oldenburg</p>
<h2>Kontakt</h2>
<p>Telefon: 0123456789<br />
E-Mail: GruppeC@uni-oldenburg.de</p>
<p>Quelle: <a href="https://www.e-recht24.de/impressum-generator.html">https://www.e-recht24.de/impressum-generator.html</a></p>
</main>
+48 -9
View File
@@ -25,16 +25,55 @@ include_once 'includes/navbar.php';
<main>
<h1>Nutzungsbedingungen</h1>
<main>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
sea takimata sanctus est Lorem ipsum dolor sit amet.
</p>
<h1>Nutzungsbedingungen</h1>
<p>
Diese Nutzungsbedingungen regeln die Nutzung der Plattform EduForge.
Mit der Registrierung und Nutzung der Website erkennen Nutzerinnen und Nutzer
die nachfolgenden Bedingungen an.
</p>
<h2>1. Nutzung der Plattform</h2>
<p>
EduForge dient dem Austausch von Lerninhalten, Beiträgen und Kommentaren.
Die Nutzung der Plattform darf ausschließlich im Rahmen der geltenden Gesetze erfolgen.
</p>
<h2>2. Verhalten der Nutzerinnen und Nutzer</h2>
<p>
Nutzerinnen und Nutzer verpflichten sich zu einem respektvollen Umgang miteinander.
Das Veröffentlichen von beleidigenden, diskriminierenden, rechtswidrigen oder
anderweitig unangemessenen Inhalten ist untersagt.
</p>
<h2>3. Veröffentlichung von Inhalten</h2>
<p>
Nutzerinnen und Nutzer sind für die von ihnen veröffentlichten Inhalte selbst verantwortlich.
Dies gilt insbesondere für Beiträge, Kommentare, Bilder und sonstige hochgeladene Dateien.
</p>
<h2>4. Urheberrechte</h2>
<p>
Es dürfen ausschließlich Inhalte veröffentlicht werden, an denen die Nutzerinnen und Nutzer
die erforderlichen Rechte besitzen. Durch das Einstellen von Inhalten versichern die
Nutzerinnen und Nutzer, keine Rechte Dritter zu verletzen.
</p>
<h2>5. Entfernung von Inhalten</h2>
<p>
Inhalte, die gegen diese Nutzungsbedingungen oder geltendes Recht verstoßen,
können vom Betreiber der Plattform entfernt werden.
</p>
<h2>6. Änderungen der Nutzungsbedingungen</h2>
<p>
Der Betreiber behält sich vor, diese Nutzungsbedingungen anzupassen, sofern dies
für den Betrieb oder die Weiterentwicklung der Plattform erforderlich ist.
</p>
</main>
</main>
+5 -10
View File
@@ -7,8 +7,8 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<main class="form-page">
<div class="flexbox">
<div class="container">
<?php include_once "includes/alertMessages.php" ?>
<?php if (!empty($error)): ?>
<p class="alert-message is-error">
@@ -93,8 +93,6 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<div class="container">
<?php include_once "includes/alertMessages.php" ?>
<h2 class="section-title">Meine Beiträge</h2>
<div class="articles-list">
@@ -182,10 +180,9 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<?php endif; ?>
</div>
</div>
<br>
<!-- Eigener Bereich für die Kommentare des Nutzers -->
<div class="container">
<div class="comments-section">
<h2 class="section-title">Meine Kommentare</h2>
@@ -228,10 +225,8 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
</div>
</div>
<?php unset($_SESSION["message"]); ?>
</div>
</div>
<?php unset($_SESSION["message"]); ?>
</div>
</main>
+66 -33
View File
@@ -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);
?>
@@ -44,12 +45,12 @@ $resultCount = count($results);
<!-- Links: Seitenleiste für Filter und Suche -->
<aside class="s-res-sidebar">
<!-- Sortierfuntion Box und Such Box-->
<form id="search-form-id" action="php/controller/search-results-controller.php" method="GET" class="s-res-sidebar-form">
<!-- Dieses Feld hält die aktuelle Seitenzahl für den Submit bereit -->
<input type="hidden" name="page" id="s-res-page-input" value="<?php echo $currentPage; ?>">
<!-- Sortierfuntion Box und Such Box-->
<form action="php/controller/search-results-controller.php" method="GET" id="search-form-id" class="s-res-sidebar-form">
<input type="hidden" id="s-res-page-input" name="page" value="<?php echo $_GET['page'] ?? 1; ?>">
<div class="s-res-sidebar-box">
<div class="s-res-sidebar-box">
<h3 class="s-res-sidebar-title">Suche anpassen</h3>
<input type="search" id="site-search" name="q" placeholder="Suchen..." class="nav__search" value="<?php echo htmlspecialchars($query); ?>" maxlength="50" required>
<button type="submit" class="nav__search-button">Suchen</button>
@@ -60,25 +61,57 @@ $resultCount = count($results);
<?php $currentSort = $_SESSION['search_sort'] ?? 'alphabet'; ?>
<div class="s-res-filter-group">
<label class="s-res-filter-option">
<input type="radio" name="sort" value="alphabet" <?php echo $currentSort === 'alphabet' ? 'checked' : ''; ?> onchange="this.form.submit()">
<input type="radio" name="sort" value="alphabet" class="sort-radio" <?php echo $currentSort === 'alphabet' ? 'checked' : ''; ?>>
<span>Alphabetisch</span>
</label>
<!-- Noch disabled, da likes noch nicht implementiert-->
<label class="s-res-filter-option">
<input type="radio" name="sort" value="likes" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?> disabled>
<span style="color: #94a3b8;">Beliebtheit (Likes)</span>
<input type="radio" name="sort" value="likes" class="sort-radio" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?>>
<span>Beliebtheit (Likes)</span>
</label>
<label class="s-res-filter-option">
<input type="radio" name="sort" value="newest" <?php echo $currentSort === 'newest' ? 'checked' : ''; ?> onchange="this.form.submit()">
<input type="radio" name="sort" value="newest" class="sort-radio" <?php echo $currentSort === 'newest' ? 'checked' : ''; ?>>
<span>Neueste Beiträge</span>
</label>
<label class="s-res-filter-option">
<input type="radio" name="sort" value="oldest" <?php echo $currentSort === 'oldest' ? 'checked' : ''; ?> onchange="this.form.submit()">
<input type="radio" name="sort" value="oldest" class="sort-radio" <?php echo $currentSort === 'oldest' ? 'checked' : ''; ?>>
<span>Älteste Beiträge</span>
</label>
</div>
</div>
<div class="s-res-sidebar-box">
<h3 class="s-res-sidebar-title">Kategorie filtern</h3>
<select id="category-filter" class="s-res-limit-select" style="width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #cbd5e1;">
<option value="all">Alle Kategorien</option>
<option value="Deutsch">Deutsch</option>
<option value="Englisch">Englisch</option>
<option value="Franzoesisch">Französisch</option>
<option value="Latein">Latein</option>
<option value="Literatur">Literatur</option>
<option value="Mathematik">Mathematik</option>
<option value="Biologie">Biologie</option>
<option value="Informatik">Informatik</option>
<option value="Chemie">Chemie</option>
<option value="Physik">Physik</option>
<option value="Astronomie">Astronomie</option>
<option value="Geschichte">Geschichte</option>
<option value="Erdkunde">Erdkunde</option>
<option value="Sozialkunde">Sozialkunde</option>
<option value="Wirtschaftskunde">Wirtschaftskunde</option>
<option value="Religion">Religion</option>
<option value="Ethikunterricht">Ethikunterricht</option>
<option value="Philosophie">Philosophie</option>
<option value="Psychologie">Psychologie</option>
<option value="Kunst">Kunst</option>
<option value="Musik">Musik</option>
<option value="Theater">Theater</option>
<option value="Technik">Technik</option>
<option value="Werken">Werken</option>
<option value="Hauswirtschaft">Hauswirtschaft</option>
<option value="Sport">Sport</option>
</select>
</div>
</form>
</aside>
@@ -95,15 +128,24 @@ $resultCount = count($results);
<?php
if (!empty($results)): ?>
<?php foreach ($results as $item): ?>
<div class="s-res-item">
<?php foreach ($results as $item):
// Anzahl der Likes ermitteln (falls es ein Array ist, zählen; falls Zahl, direkt nutzen)
$likesCount = isset($item['likes']) && is_array($item['likes']) ? count($item['likes']) : ($item['likes'] ?? 0);
?>
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
<div class="s-res-content">
<h2 class="s-res-item-title">
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
<?php echo htmlspecialchars($item['title']); ?>
</a>
</h2>
<p class="s-res-author">Von: <span class="s-res-author-name"><?php echo htmlspecialchars($item['author']); ?></span></p>
<div class="s-res-meta-row">
<p class="s-res-author">Von: <span class="s-res-author-name"><?php echo htmlspecialchars($item['author']); ?></span></p>
<span class="s-res-likes">
❤️ <?php echo $likesCount; ?>
</span>
</div>
</div>
<div class="s-res-arrow">&rarr;</div>
</div>
@@ -113,8 +155,8 @@ $resultCount = count($results);
elseif (isset($_SESSION["search_query"]) && $_SESSION["search_query"] !== "" && $resultCount === 0): ?>
<p>Keine Beiträge zu diesem Suchbegriff gefunden.</p>
<?php
elseif (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_parameters"): ?>
<p>Bitte überprüfe deine Sucheingabe und versuche es erneut!</p>
elseif (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_search_query"): ?>
<p>Unzulässige Suchanfrage</p>
<?php endif; ?>
<?php
@@ -127,7 +169,7 @@ $resultCount = count($results);
<!-- Auswahl der Ergebnisse pro Seite -->
<div class="s-res-limit-selector">
<label for="s-res-per-page" class="s-res-limit-label">Ergebnisse pro Seite:</label>
<select id="s-res-per-page" name="limit" class="s-res-limit-select" form="search-form-id" onchange="this.form.submit()">
<select id="s-res-per-page" name="limit" class="s-res-limit-select">
<option value="10" <?php echo $limit === 10 ? 'selected' : ''; ?>>10</option>
<option value="20" <?php echo $limit === 20 ? 'selected' : ''; ?>>20</option>
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>50</option>
@@ -136,23 +178,14 @@ $resultCount = count($results);
</div>
<div class="s-res-page-navigation">
<button type="button" class="s-res-page-btn" data-page="<?php echo $currentPage - 1; ?>" <?php echo $currentPage <= 1 ? 'disabled' : ''; ?>>
«
</button>
<!-- Dynamische Seitenzahlen -->
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<button type="button"
class="s-res-page-btn <?php echo $i === $currentPage ? 's-res-page-btn-active' : ''; ?>"
data-page="<?php echo $i; ?>">
<?php echo $i; ?>
</button>
<?php endfor; ?>
<button type="button" class="s-res-page-btn" data-page="<?php echo $currentPage + 1; ?>" <?php echo $currentPage >= $totalPages ? 'disabled' : ''; ?>>
»
</button>
<button type="button" class="s-res-page-btn" id="prev-page-btn" data-page="0">«</button>
<span id="dynamic-page-numbers"></span>
<button type="button" class="s-res-page-btn" id="next-page-btn" data-page="2">»</button>
</div>
</div>
</main>
</div>
</div>
+24 -23
View File
@@ -1,28 +1,10 @@
<?php
include_once 'php/controller/showArticle-controller.php';
require_once 'php/model/CommentManager.php';
$comments = [];
$mainComments = [];
$repliesByParent = [];
$articleObj = null;
if (isset($_GET["id"])) {
try {
$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";
}
}
include_once 'php/controller/showArticle-controller.php';
?>
<!--
Seite: Anzeige für Beiträge
@@ -35,9 +17,28 @@ if (isset($_GET["id"])) {
<!-- Metadaten & Titel -->
<div class="article-view-top-section">
<?php if (isset($category) && !empty($category)): ?>
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
<?php endif; ?>
<div class="article-view-top-section">
<div class="category-and-likes-row">
<?php if (isset($category) && !empty($category)): ?>
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
<?php endif; ?>
<!-- Like-Anzeige und dynamischer Like-Button -->
<?php if (isset($articleObj) && $articleObj !== null): ?>
<div class="article-view-likes">
<span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span>
<?php if (isset($_SESSION["user_email"])): ?>
<a href="php/controller/like-controller.php?id=<?php echo $articleObj->getId(); ?>" class="like-toggle-btn">
<?php echo $articleObj->hasLiked($_SESSION["user_email"]) ? '👎 Gefällt mir nicht mehr' : '👍 Gefällt mir'; ?>
</a>
<?php else: ?>
<span class="login-hint">(Anmelden zum Liken)</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<h1 class="article-view-title">
<?php if (isset($title)) { echo htmlspecialchars($title); } ?>
+11
View File
@@ -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 {
+44
View File
@@ -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;
}
+10
View File
@@ -63,6 +63,16 @@
Dein Beitrag wurde erfolgreich veröffentlicht!
</p>
<?php endif; ?>
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "article_updated"): ?>
<p class="alert-message is-success">
Der Beitrag wurde erfolgreich bearbeitet und gespeichert.
</p>
<?php endif; ?>
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "profile_updated"): ?>
<p class="alert-message is-success">
Das Profil wurde erfolgreich bearbeitet.
</p>
<?php endif; ?>
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "image_upload_error"): ?>
<p class="alert-message is-error">
Das Bild konnte nicht hochgeladen werden. Bitte versuche es erneut oder verwende ein anderes Bildformat.
+1
View File
@@ -2,4 +2,5 @@
<a href="index.php?pfad=impressum">Impressum</a>
<a href="index.php?pfad=datenschutz">Datenschutz</a>
<a href="index.php?pfad=nutzungsbedingungen">Nutzungsbedingungen</a>
<a href="index.php?pfad=accessibility">Barrierefreiheit</a>
</footer>
+2
View File
@@ -53,8 +53,10 @@ if ($pfad === "deleteAccount") {
<link rel="stylesheet" href="css/message.css">
<script src="js/paginator.js" async></script>
<script src="js/sorter.js" async></script>
<script src="js/comments.js" defer></script>
<script src="js/editor.js" async></script>
<script src="js/filter.js" async></script>
<title>EduForge</title>
</head>
+116
View File
@@ -0,0 +1,116 @@
let currentClientPage = 1;
let itemsPerPage = 10;
function initFilter() {
const filterSelect = document.getElementById('category-filter');
const listContainer = document.querySelector('.s-res-list');
const limitSelect = document.getElementById('s-res-per-page');
if (!filterSelect || !listContainer) return;
if (limitSelect) {
itemsPerPage = parseInt(limitSelect.value, 10) || 10;
}
updateVisibility();
if (limitSelect) {
limitSelect.addEventListener('change', function() {
//neues limit einlesen
itemsPerPage = parseInt(this.value, 10);
currentClientPage = 1;
updateVisibility();
});
}
filterSelect.addEventListener('change', function() {
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;
e.preventDefault();
const targetPage = button.getAttribute('data-page');
if (targetPage) {
currentClientPage = parseInt(targetPage, 10);
updateVisibility();
}
});
}
}
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');
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';
}
});
const totalVisible = visibleCards.length;
const totalPages = Math.max(1, Math.ceil(totalVisible / itemsPerPage));
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';
}
});
updatePaginatorUI(currentClientPage, totalPages);
}
function updatePaginatorUI(currentPage, totalPages) {
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 += `<button type="button" class="s-res-page-btn ${activeClass}" data-page="${i}">${i}</button> `;
}
numbersContainer.innerHTML = buttonsHTML;
}
// 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();
}
+50
View File
@@ -0,0 +1,50 @@
function initSorter() {
const listContainer = document.querySelector('.s-res-list');
const sortRadios = document.querySelectorAll('.sort-radio');
// wenn keine liste vorhanden, abbrechen
if (!listContainer || sortRadios.length === 0) return;
sortRadios.forEach(radio => {
radio.addEventListener('change', function() {
const cards = Array.from(listContainer.querySelectorAll('.s-res-item'));
const sortValue = this.value;
cards.sort((a, b) => {
if (sortValue === 'likes') {
const likesA = parseInt(a.getAttribute('data-likes') || '0', 10);
const likesB = parseInt(b.getAttribute('data-likes') || '0', 10);
return likesB - likesA;
}
else if (sortValue === 'alphabet') {
// alphabetische sortierung
const titleA = a.querySelector('.s-res-link').textContent.trim().toLowerCase();
const titleB = b.querySelector('.s-res-link').textContent.trim().toLowerCase();
return titleA.localeCompare(titleB);
}
else if (sortValue === 'newest' || sortValue === 'oldest') {
// hoehere ID wird als neuer gesehen
const urlA = a.querySelector('.s-res-link').getAttribute('href');
const urlB = b.querySelector('.s-res-link').getAttribute('href');
const idA = parseInt(urlA.match(/id=(\d+)/)[1], 10);
const idB = parseInt(urlB.match(/id=(\d+)/)[1], 10);
return sortValue === 'newest' ? idB - idA : idA - idB;
}
return 0;
});
listContainer.innerHTML = '';
cards.forEach(card => listContainer.appendChild(card));
});
});
}
// ist das DOM bereits vollständig aufgebaut?
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSorter);
} else {
initSorter();
}
@@ -6,6 +6,10 @@ require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php';
require_once '../validator/article-validator.php';
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$_SESSION["old_title"] = $_POST["title"] ?? '';
$_SESSION["old_content"] = $_POST["content"] ?? '';
@@ -6,6 +6,11 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . "/../model/UserManager.php";
require_once __DIR__ . "/../model/ArticleManager.php";
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
/*
Deregistrierung
Funktion: Entfernt User aus der Datenbank und beendet die Session
@@ -5,6 +5,11 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . "/../model/ArticleManager.php";
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (isset($_SESSION["user_email"])) {
+42
View File
@@ -0,0 +1,42 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../model/Article.php';
require_once __DIR__ . '/../model/ArticleManager.php';
// 2. Prüfen, ob eine gültige Artikel-ID übergeben wurde
if (isset($_GET["id"]) && !empty($_GET["id"])) {
$articleId = intval($_GET["id"]);
$userEmail = $_SESSION["user_email"];
if (!isset($_SESSION["user_email"]) || empty($_SESSION["user_email"])) {
$_SESSION["message"] = "unauthorized_access";
header("Location: ../../index.php?pfad=showArticle&id=" . $articleId);
exit();
}
try {
$articleManager = ArticleManager::getInstance();
$articleManager->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();
}
?>
+1
View File
@@ -63,6 +63,7 @@ try {
$_SESSION["user"] = $vorname . " " . $nachname;
$_SESSION["user_email"] = $newEmail;
$_SESSION["message"] = "profile_updated";
header("Location: index.php?pfad=profile");
exit();
} else {
+21 -12
View File
@@ -5,14 +5,15 @@ 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";
$_SESSION["message"] = "invalid_search_query";
} else {
try {
@@ -25,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());
});
}
@@ -44,13 +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
"id" => $obj->getId(),
"title" => $obj->getTitle(),
"content" => $obj->getContent(),
"author" => $obj->getAuthor(),
"category" => $obj->getCategory(),
"tags" => $obj->getTags(),
"creationDate" => $obj->getCreationDate(),
"likes" => $obj->getLikes(),
];
}
@@ -65,6 +71,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();
+15
View File
@@ -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();
+20 -1
View File
@@ -8,10 +8,15 @@ require_once '../model/ArticleManager.php';
require_once '../model/Article.php';
require_once '../validator/article-validator.php';
if (!isset($_SESSION["user_email"])) {
header("Location: index.php?pfad=login");
exit();
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$_SESSION["old_title"] = $_POST["title"] ?? '';
$_SESSION["old_content"] = $_POST["content"] ?? '';
$_SESSION["old_category"] = $_POST["category"] ?? ''; // TODO: die Kategorie im Dropdown setzen, wenn der Editor erneut geöffnet wird.
$_SESSION["old_category"] = $_POST["category"] ?? '';
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
if (isset($_GET["id"]) && !empty($_GET["id"])) {
@@ -22,6 +27,20 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
exit();
}
try {
$articleManager = ArticleManager::getInstance();
$article = $articleManager->getArticle($id);
if ($article->getAuthor() != $_SESSION["user_email"]) {
$_SESSION["message"] = "unauthorized_access";
header("location: ../../index.php");
exit();
}
} catch (Exception $e) {
$_SESSION["message"] = $e->getMessage();
header("location: ../../index.php?pfad=updateArticle&id=$id");
exit();
}
if (!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
$_SESSION["message"] = "missing_parameters";
header("location: ../../index.php?pfad=updateArticle&id=$id");
+39 -10
View File
@@ -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);
}
}
?>
+12
View File
@@ -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;
}
?>
+91 -4
View File
@@ -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");
}
}
}
+64 -6
View File
@@ -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;
}
}
?>
+40
View File
@@ -0,0 +1,40 @@
<?php
/**
* Prüft, ob eine Suchanfrage gültig ist.
*
* Erlaubt werden Buchstaben, Zahlen, Umlaute, typische Satzzeichen und Leerzeichen.
* Die Länge muss zwischen 1 und 50 Zeichen liegen.
*
* @param string $query Zu prüfender Suchbegriff
*
* @return bool true wenn die Suche gültig ist, sonst false
*/
function searchQueryValidator($query)
{
$query = trim($query);
// Mindestens 1 Zeichen, maximal 50 Zeichen
$length = mb_strlen($query);
if ($length < 1 || $length > 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);
}