Compare commits
266 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0aca76d2f4 | |||
| eca6646071 | |||
| 35e5006063 | |||
| fcb9289d80 | |||
| 6595a3d78d | |||
| 41d8ac6721 | |||
| 63c213bae7 | |||
| 61d72436c9 | |||
| 721bc8d833 | |||
| 2666d2d14f | |||
| de1ba88860 | |||
| e0fb04794d | |||
| 6897a29574 | |||
| 8836034670 | |||
| d590561ade | |||
| ba11c216bd | |||
| 71a1b029cb | |||
| 2126d076d5 | |||
| 2ecd446dd0 | |||
| 0b4c5f988d | |||
| 59eeb05e4b | |||
| 5d8239822a | |||
| 31585511d0 | |||
| 2ec1b70c91 | |||
| 8f8b25249c | |||
| 8be6e6d61c | |||
| bad0c08e6f | |||
| 55695f3f82 | |||
| 74cbc60cbe | |||
| c09c7ecfa3 | |||
| 944e332b49 | |||
| 4306785be9 | |||
| 266b70a094 | |||
| bd8593d212 | |||
| 0d009262b6 | |||
| bb2c368b1f | |||
| a712f5a993 | |||
| 53bee08833 | |||
| 7a2a3ac1e9 | |||
| a5f02c9a6c | |||
| f77f09ec6f | |||
| 535df85cff | |||
| 82e8c8a1a3 | |||
| c0d8fdede6 | |||
| 958ee1c1f5 | |||
| 1dd128d480 | |||
| dca716ddc5 | |||
| c1dd4fce2b | |||
| 16cfa92721 | |||
| 3b2ed78c1c | |||
| cebbe8869a | |||
| e07051b356 | |||
| 2d2bbeb39f | |||
| c00443b6fa | |||
| 1d541e285c | |||
| b96c588697 | |||
| fde9c59f91 | |||
| 72c7363a07 | |||
| 0b36050558 | |||
| 00dbf83da4 | |||
| c3e060b963 | |||
| a382d9254e | |||
| 2b7f44b817 | |||
| b5e4f44252 | |||
| 8561b02a08 | |||
| 01f4ff2d28 | |||
| b7e4387373 | |||
| e9a92eaec7 | |||
| 6aec353d50 | |||
| 7a4f2d738f | |||
| a0a469a9df | |||
| 54209929fe | |||
| 5321534d71 | |||
| 7cf542c270 | |||
| c45d23a6b3 | |||
| ab094429b7 | |||
| cc5f46c00e | |||
| 1b2ab1cccb | |||
| adbf488e01 | |||
| 15ea5ddbfb | |||
| b93ca321a2 | |||
| 97847c76c4 | |||
| 3dae5ee720 | |||
| cbb662d159 | |||
| c5e7439541 | |||
| a748d94305 | |||
| 6c1a893f80 | |||
| 788e803e84 | |||
| 4d0f1de01c | |||
| e71ee8300e | |||
| f39885da10 | |||
| b7ca5bcfe2 | |||
| 57b0782577 | |||
| 4f524db221 | |||
| c2b7462307 | |||
| cdbf81878b | |||
| 68ec6dedd7 | |||
| 12782463d7 | |||
| 21a935edf3 | |||
| 3d65f79d2c | |||
| ed31210e3a | |||
| 5030caa029 | |||
| 4a87152531 | |||
| 1401c3afe7 | |||
| d5a3f4298f | |||
| b4a5c9c020 | |||
| 6d6f1f31b0 | |||
| cafffba921 | |||
| 8fa758d825 | |||
| 17034c928b | |||
| 395041fd44 | |||
| c117c7e641 | |||
| 01fc8537cf | |||
| 12ad9759cf | |||
| 057639be7c | |||
| 87e7be1c10 | |||
| 1db77346d0 | |||
| 1e6ac0fcd9 | |||
| b3c74c2172 | |||
| 0805958033 | |||
| 5c0e978c93 | |||
| 520298bae4 | |||
| 969adc4836 | |||
| d9eca06197 | |||
| 48f2e90e49 | |||
| 358247a2a1 | |||
| de4d2fe5c0 | |||
| e1102eb7db | |||
| a8df9590fd | |||
| 3e453e22ec | |||
| beeab0ec90 | |||
| 9353a7eaaa | |||
| cac8f3046d | |||
| 66eeac372c | |||
| 4a6a3cf708 | |||
| cb44c3f5a8 | |||
| eb0a34e959 | |||
| 8d683cc1a0 | |||
| ee41dd4cfe | |||
| 3d50e6e3b3 | |||
| 4f8d11881d | |||
| 5c924d3277 | |||
| 7f703a8386 | |||
| f063ea4741 | |||
| ca643dd298 | |||
| e19225e49e | |||
| 9ffbca679e | |||
| d90a10e462 | |||
| b6f25d041b | |||
| 4321c4bee5 | |||
| d1b3641754 | |||
| 5281c1bc7d | |||
| aae78bf479 | |||
| af17764346 | |||
| a8d321609d | |||
| 23ccbe2d02 | |||
| 47239016c5 | |||
| f4f6960b9d | |||
| c4c299eed6 | |||
| e20b59b43d | |||
| 901232b683 | |||
| 40c5fa69f5 | |||
| 7694396454 | |||
| dafc149bba | |||
| f23ec27247 | |||
| efb179dec2 | |||
| 86222aa92e | |||
| a5fb5cd360 | |||
| 6f6e53a483 | |||
| f13a2c6f1e | |||
| 59012cacfb | |||
| 21c5471d73 | |||
| e1ee70b46f | |||
| eb56b8d6a0 | |||
| d597046f41 | |||
| ae1c873536 | |||
| 1f7a2a77f6 | |||
| 9269465f6b | |||
| 883adfd242 | |||
| 1e890f9a28 | |||
| 2bb13d2e8c | |||
| cb02e05d55 | |||
| 05777f6109 | |||
| f3f042b3b9 | |||
| dfb13b1f96 | |||
| 6fa5038589 | |||
| 55f53e3346 | |||
| f36037d36d | |||
| 500bca80c1 | |||
| fc0361ed3a | |||
| df1485ce49 | |||
| b8bceb31c6 | |||
| f0308c3505 | |||
| e7ece1ed13 | |||
| 8ed7f7ec8f | |||
| feaef4a624 | |||
| 7d9aae2a59 | |||
| a88f8ca638 | |||
| be6a5f9add | |||
| ed4498578d | |||
| 118be50b3c | |||
| 1e2184ae20 | |||
| 8c1e1a64bf | |||
| b9c28968fe | |||
| 703d8d7519 | |||
| 8e52d99ba4 | |||
| d4020671c1 | |||
| 5c432e5801 | |||
| aac13297de | |||
| f60a4a4f60 | |||
| 0ed4de74b0 | |||
| 48bd26342a | |||
| b8e884a5aa | |||
| 1dbef5ca4b | |||
| e43ae7f96a | |||
| eab256d17f | |||
| bbdcbbf0ce | |||
| ec9bc3fe1f | |||
| 80f92a384e | |||
| 95251d23ad | |||
| c16bf574ce | |||
| 0e44d92baa | |||
| f9c1c67a38 | |||
| 286e2b6237 | |||
| fb78794261 | |||
| 371a65d361 | |||
| 7bd3c8dfd1 | |||
| e5aa6ea25d | |||
| de9afd3275 | |||
| 79e9f17f70 | |||
| 12cc3c5d5b | |||
| 404cea74ff | |||
| 16e94947f2 | |||
| aff0b0560f | |||
| de19a6fc65 | |||
| 1f669aadfc | |||
| 33f428559c | |||
| 0c3b9eb250 | |||
| c654fab94a | |||
| f2ef3c08a7 | |||
| 8a66e8af3f | |||
| 41d1cc6013 | |||
| c9c6a4ae81 | |||
| a5131e33dc | |||
| a19d1db990 | |||
| b6e7da6808 | |||
| f21fab937f | |||
| 30fccfe69b | |||
| b692d76f7d | |||
| 56533d4a16 | |||
| 8a22b475f9 | |||
| ae3a1be5e1 | |||
| 8199afc88f | |||
| 2d7ef8fc1d | |||
| d75d78ccb3 | |||
| 42489f50b6 | |||
| 52fde19380 | |||
| b1fb3e5baa | |||
| 5f3dae64c1 | |||
| b31ce66ef9 | |||
| 93bd561ff9 | |||
| d0d93be96b | |||
| 327a05cf63 | |||
| 1394467abe | |||
| ace73f15ef | |||
| 12facddcd5 |
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="dataSourceStorageLocal" created-in="IU-261.24374.151">
|
<component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
|
||||||
<data-source name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
|
<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">
|
<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>"</identifier-quote-string>
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
|||||||
@@ -6,30 +6,37 @@
|
|||||||
- Niklas Ortmann
|
- Niklas Ortmann
|
||||||
|
|
||||||
## Login-Informationen für Dummy-User
|
## Login-Informationen für Dummy-User
|
||||||
Jeweils im Format `Anmeldename, Passwort, Mailadresse`:
|
`Anmeldename, Passwort, Mailadresse`:
|
||||||
- `max.mustermann, test123, mustermann@web.de`
|
- `max.mustermann, test12345, max.mustermann@web.de`
|
||||||
|
|
||||||
## Weitere Voraussetzungen zur Nutzung
|
## Weitere Voraussetzungen zur Nutzung
|
||||||
- Per Klick auf das Logo gelangt man auf die Home-Seite.
|
- Per Klick auf das Logo gelangt man auf die Home-Seite.
|
||||||
|
|
||||||
## Ausgelassene Teilaufgaben
|
## Ausgelassene Teilaufgaben
|
||||||
|
- Das Anerkennen der Datenschutzerklärung und das Einhalten der
|
||||||
|
Nutzungsbedingungen (bspw. was Rechte an eingestellten Texten und
|
||||||
|
hochgeladenen Fotos angeht) muss ein Nutzer bei der Registrierung via einer
|
||||||
|
Checkbox und dem Opt-In-Verfahren fehlt noch.
|
||||||
|
|
||||||
## Bekannte Fehler und Mängel
|
## Bekannte Fehler und Mängel
|
||||||
- Der Beitragseditor fügt beim Laden von Inhalten leere Zeichen ein.
|
|
||||||
- Bitte auf die gesetzten TODO's achten. Wenn Inhalte fehlen, sind sie i.d.R. als TODO kommentiert.
|
- Bitte auf die gesetzten TODO's achten. Wenn Inhalte fehlen, sind sie i.d.R. als TODO kommentiert.
|
||||||
- Die Navbar hat noch keine onHover-Effekte und erschwert etwas die Bedienung.
|
- Die Kategorieseite listet momentan alle passenden Beiträge untereinander. Später sollen mit einem Paginator die neusten
|
||||||
- Suchleiste in der Nav ist in der mobilen Ansicht etwas schmal. Ggf. ein alternatives (kleineres) Logo implementieren.
|
Beiträge nacheinander aufgelistet werden (ähnlich wie bei der Suche, wenn nach Fach gefiltert wird).
|
||||||
- Die Suchseite und Kategorieseite packen momentan alle passenden Beiträge untereinander. Später sollen zunächst 10
|
- Wenn ein Bild aus einem Beitrag entfernt wird, dann wird noch nicht die Datei im Pfad /uploads gelöscht.
|
||||||
Ergebnisse auf einer Seite angezeigt werden.
|
- 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.
|
||||||
|
- Die Datenbank ist noch in die verschiedenen Manager unterteilt. Zukünftig werden alle Daten in einer Datenbank gespeichert.
|
||||||
|
|
||||||
## Besonderheiten des Projektes
|
## Besonderheiten des Projektes
|
||||||
- Es wurde ein einfacher Beitrags-Editor erstellt. Mit diesem können Beiträge erstellt oder bearbeitet werden.
|
- Es wurde AJAX verwendet, um asynchrone Erstellung von Kommentaren zu implementieren. Es ermöglicht dem Nutzer, einen
|
||||||
Es handelt es sich um eine einfache Version. Später sollen z.B. Bilder und die Positionierung der Elemente folgen.
|
Kommentar abzusenden, ohne dass die gesamte Webseite neu geladen werden muss.
|
||||||
- Es sind drei Dummy-Beiträge für den Nutzer max.mustermann hinterlegt.
|
- Mit JavaScript werden auch clientseitig die Kommentare visuell hinzugefügt und die Kommentarbäume aufgebaut.
|
||||||
- Beim Verwenden der Navbar-Links bitte Folgendes beachten: nur die Kategorien Physik, Mathe, Informatik führen
|
- JavaScript wird verwendet, um im erweitertem Beitragseditor clientseitig einzelne Content-Boxen erstellen und löschen
|
||||||
exemplarisch auf eine Beispiel-Seite.
|
zu können.
|
||||||
Die anderen Links sind erstmal Platzhalter und führen auf eine 404-Seite.
|
- JavaScript wird ebenfalls verwendet, um in die Suchergebnisse clientseitig zu sortieren.
|
||||||
- Die Such-Seite umfasst eine Suchfunktion (aber noch nicht nach Tags) und Sortierfunktion. Jedoch fehlt noch eine
|
|
||||||
Filterfunktion (z.B. nur Mathe anzeigen).
|
## Sonstiges
|
||||||
Wenn die Filterfunktion implementiert ist, dann können die statischen Seiten "Informatik", "Mathe", "Physik" entfernt
|
- Das Datenschema befindet sich unter /planung/Datenschema.pdf
|
||||||
und dynamisch auf der Suchseite nach gefiltertem Fach angezeigt werden. (Vorläufige showCategory.php implementiert).
|
- Im Impressum, sowie der Datenschutzerklaerung wurden Dummy-Daten verwendet
|
||||||
+1
-2
@@ -14,7 +14,6 @@
|
|||||||
?>
|
?>
|
||||||
<h1>404 - Seite nicht vorhanden</h1>
|
<h1>404 - Seite nicht vorhanden</h1>
|
||||||
<p>
|
<p>
|
||||||
Später im Projekt sollen über index.php?pfad= ... der Inhalt der index.php dynamisch gesetzt werden.
|
Die Seite, nach der du suchst, existiert hier leider nicht. Entweder haben wir sie verlegt, ein hungriger Bug hat sie gefressen, oder du hast dich beim Tippen vertippt. (Wir schieben die Schuld einfach mal ganz unauffällig auf dich.)
|
||||||
Alle Content-Links der Navbar führen zunächst hier her. Exemplarisch wurden für die Fächer Informatik, Physik, Mathematik der Content als Seite erstellt und kann über die Navbar ausgekundschaftet werden.
|
|
||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/** @var string $title */
|
||||||
|
/** @var string $message */
|
||||||
|
/** @var string $link */
|
||||||
|
/** @var bool $isSuccess */
|
||||||
|
|
||||||
|
$title = $title ?? "Passwort zurücksetzen";
|
||||||
|
$message = $message ?? "Der Bestätigungslink konnte nicht verarbeitet werden.";
|
||||||
|
$link = $link ?? "";
|
||||||
|
$isSuccess = $isSuccess ?? false;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ansicht: Passwortbestätigung
|
||||||
|
Funktion: Zeigt das Ergebnis der Passwortänderung an.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1><?php echo htmlspecialchars($title); ?></h1>
|
||||||
|
|
||||||
|
<p class="alert-message <?php echo $isSuccess ? 'is-success' : 'is-error'; ?> confirm-message">
|
||||||
|
<?php echo htmlspecialchars($message); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php echo $link; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
/** @var string $title */
|
||||||
|
/** @var string $message */
|
||||||
|
/** @var string $link */
|
||||||
|
/** @var bool $isSuccess */
|
||||||
|
|
||||||
|
$title = $title ?? "Registrierung";
|
||||||
|
$message = $message ?? "Der Registrierungslink konnte nicht verarbeitet werden.";
|
||||||
|
$link = $link ?? "";
|
||||||
|
$isSuccess = $isSuccess ?? false;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ansicht: Registrierungsbestätigung
|
||||||
|
Funktion: Zeigt das Ergebnis der Registrierungsbestätigung an.
|
||||||
|
-->
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1><?php echo htmlspecialchars($title); ?></h1>
|
||||||
|
|
||||||
|
<p class="alert-message <?php echo $isSuccess ? 'is-success' : 'is-error'; ?> confirm-message">
|
||||||
|
<?php echo htmlspecialchars($message); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php echo $link; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
+216
-66
@@ -12,49 +12,33 @@ if (!isset($_SESSION["user"])) {
|
|||||||
<form method="post" action="php/controller/createArticle-controller.php" id="editor-form" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
<form method="post" action="php/controller/createArticle-controller.php" id="editor-form" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
||||||
|
|
||||||
<main class="editor-main">
|
<main class="editor-main">
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein interner Fehler beim Speichern aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_parameters"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Jeder Beitrag muss einen Titel, Kategorie und Inhalt besitzen.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_title"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Der Titel enthält ungültige Zeichen oder erfüllt die Länge von 5-120 Zeichen nicht.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_content"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Der Text erlaubt eine Länge von 10 bis maximal 7.000 Zeichen (ca. 1.000 Wörter).
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Die ausgewählte Kategorie ist ungültig.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_tags"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Ungültige Schlagworte gefunden. Erlaubt sind nur Buchstaben, Zahlen, Leerzeichen und Bindestriche (2-50 Zeichen).
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "validation_missing"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Bei der Validierung deiner Daten ist ein Fehler aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php
|
|
||||||
unset($_SESSION["message"]);
|
|
||||||
?>
|
|
||||||
<input type="text" id="title" name="title"
|
<input type="text" id="title" name="title"
|
||||||
value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>"
|
value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>"
|
||||||
placeholder="Titel hier eingeben" required>
|
placeholder="Titel hier eingeben" required>
|
||||||
<textarea id="content" name="content" placeholder="Schreibe deinen Beitrag..."><?php if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){echo htmlspecialchars($_SESSION['old_content']); unset($_SESSION['old_content']);}elseif (isset($content) && !empty($content)){echo htmlspecialchars($content);}?></textarea>
|
|
||||||
|
|
||||||
|
<!-- Hier werden die dynamischen divs via JavaScript eingefügt -->
|
||||||
|
<div id="block-container"></div>
|
||||||
|
|
||||||
|
<!-- Plus-Button und das Pop-up-Menü -->
|
||||||
|
<div id="add-block-control" class="article-editor-scope add-block-control">
|
||||||
|
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
||||||
|
<div id="block-popup" class="article-editor-scope block-popup hidden">
|
||||||
|
<button type="button" data-type="text">Textblock</button>
|
||||||
|
<button type="button" data-type="image">Bild einfügen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unsichtbares Textfeld, das die JSON-Daten hält und an den Controller postet -->
|
||||||
|
<textarea id="content" name="content" style="display:none;"><?php
|
||||||
|
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){
|
||||||
|
echo htmlspecialchars($_SESSION['old_content']);
|
||||||
|
unset($_SESSION['old_content']);
|
||||||
|
} else {
|
||||||
|
echo '[]'; // Standardmäßig ein leeres JSON-Array
|
||||||
|
}
|
||||||
|
?></textarea>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Seitenleiste -->
|
<!-- Seitenleiste -->
|
||||||
@@ -67,45 +51,211 @@ if (!isset($_SESSION["user"])) {
|
|||||||
<div class="sidebar-block">
|
<div class="sidebar-block">
|
||||||
<label for="category">Kategorie <span class="required">*</span></label>
|
<label for="category">Kategorie <span class="required">*</span></label>
|
||||||
<select id="category" name="category" required>
|
<select id="category" name="category" required>
|
||||||
<option value="" disabled selected>Kategorie wählen...</option>
|
<option disabled <?php
|
||||||
|
if ((!isset($_SESSION['old_category']) || empty($_SESSION['old_category'])) && (!isset($category) || empty($category))) {
|
||||||
|
echo 'selected';
|
||||||
|
}
|
||||||
|
?>>Kategorie wählen...</option>
|
||||||
|
|
||||||
<optgroup label="Sprachen">
|
<optgroup label="Sprachen">
|
||||||
<option value="deutsch">Deutsch</option>
|
<option value="deutsch" <?php
|
||||||
<option value="englisch">Englisch</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="franzoesisch">Französisch</option>
|
if ($_SESSION['old_category'] === 'deutsch') { echo 'selected'; }
|
||||||
<option value="latein">Latein</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="literatur">Literatur</option>
|
if ($category === 'deutsch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Deutsch</option>
|
||||||
|
<option value="englisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'englisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'englisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Englisch</option>
|
||||||
|
<option value="franzoesisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'franzoesisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'franzoesisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Französisch</option>
|
||||||
|
<option value="latein" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'latein') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'latein') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Latein</option>
|
||||||
|
<option value="literatur" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'literatur') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'literatur') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Literatur</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="MINT">
|
<optgroup label="MINT">
|
||||||
<option value="mathe">Mathematik</option>
|
<option value="mathe" <?php
|
||||||
<option value="biologie">Biologie</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="chemie">Chemie</option>
|
if ($_SESSION['old_category'] === 'mathe') { echo 'selected'; }
|
||||||
<option value="physik">Physik</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="informatik">Informatik</option>
|
if ($category === 'mathe') { echo 'selected'; }
|
||||||
<option value="astronomie">Astronomie</option>
|
}
|
||||||
|
?>>Mathematik</option>
|
||||||
|
<option value="biologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'biologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'biologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Biologie</option>
|
||||||
|
<option value="chemie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'chemie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'chemie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Chemie</option>
|
||||||
|
<option value="physik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'physik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'physik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Physik</option>
|
||||||
|
<option value="informatik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'informatik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'informatik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Informatik</option>
|
||||||
|
<option value="astronomie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'astronomie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'astronomie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Astronomie</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="Gesellschaft & Werte">
|
<optgroup label="Gesellschaft & Werte">
|
||||||
<option value="geschichte">Geschichte</option>
|
<option value="geschichte" <?php
|
||||||
<option value="erdkunde">Erdkunde</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="sozialkunde">Sozialkunde</option>
|
if ($_SESSION['old_category'] === 'geschichte') { echo 'selected'; }
|
||||||
<option value="wirtschaft">Wirtschaftskunde</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="religion">Religion</option>
|
if ($category === 'geschichte') { echo 'selected'; }
|
||||||
<option value="ethik">Ethikunterricht</option>
|
}
|
||||||
<option value="philosophie">Philosophie</option>
|
?>>Geschichte</option>
|
||||||
<option value="psychologie">Psychologie</option>
|
<option value="erdkunde" <?php
|
||||||
<option value="kunst">Kunst</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="musik">Musik</option>
|
if ($_SESSION['old_category'] === 'erdkunde') { echo 'selected'; }
|
||||||
<option value="theater">Theater</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'erdkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Erdkunde</option>
|
||||||
|
<option value="sozialkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sozialkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sozialkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sozialkunde</option>
|
||||||
|
<option value="wirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'wirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'wirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Wirtschaftskunde</option>
|
||||||
|
<option value="religion" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'religion') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'religion') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Religion</option>
|
||||||
|
<option value="ethik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'ethik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'ethik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Ethikunterricht</option>
|
||||||
|
<option value="philosophie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'philosophie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'philosophie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Philosophie</option>
|
||||||
|
<option value="psychologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'psychologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'psychologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Psychologie</option>
|
||||||
|
<option value="kunst" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'kunst') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'kunst') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Kunst</option>
|
||||||
|
<option value="musik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'musik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'musik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Musik</option>
|
||||||
|
<option value="theater" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'theater') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'theater') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Theater</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="Technik & Praxis">
|
<optgroup label="Technik & Praxis">
|
||||||
<option value="technik">Technik</option>
|
<option value="technik" <?php
|
||||||
<option value="werken">Werken</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="hauswirtschaft">Hauswirtschaft</option>
|
if ($_SESSION['old_category'] === 'technik') { echo 'selected'; }
|
||||||
<option value="sport">Sport</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'technik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Technik</option>
|
||||||
|
<option value="werken" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'werken') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'werken') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Werken</option>
|
||||||
|
<option value="hauswirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Hauswirtschaft</option>
|
||||||
|
<option value="sport" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sport') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sport') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sport</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['old_category'])) {
|
||||||
|
unset($_SESSION['old_category']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+180
-9
@@ -4,15 +4,186 @@
|
|||||||
-->
|
-->
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
<h1>Datenschutz</h1>
|
<h1>Datenschutz­erklärung</h1>
|
||||||
|
<h2>1. Datenschutz auf einen Blick</h2>
|
||||||
|
<h3>Allgemeine Hinweise</h3> <p>Die folgenden Hinweise geben einen einfachen Überblick darüber,
|
||||||
|
was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind
|
||||||
|
alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum
|
||||||
|
Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten
|
||||||
|
Datenschutzerklärung.</p>
|
||||||
|
<h3>Datenerfassung auf dieser Website</h3> <h4>Wer ist verantwortlich für die Datenerfassung auf dieser
|
||||||
|
Website?</h4> <p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen
|
||||||
|
Kontaktdaten können Sie dem Abschnitt „Hinweis zur verantwortlichen Stelle“ in dieser
|
||||||
|
Datenschutzerklä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. 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. B. Internetbrowser,
|
||||||
|
Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese
|
||||||
|
Website betreten.</p> <h4>Wofür nutzen wir Ihre Daten?</h4> <p>Ein Teil der Daten wird erhoben, um eine
|
||||||
|
fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres
|
||||||
|
Nutzerverhaltens verwendet werden. Sofern über die Website Verträge geschlossen oder angebahnt
|
||||||
|
werden können, werden die übermittelten Daten auch für Vertragsangebote, Bestellungen oder
|
||||||
|
sonstige Auftragsanfragen verarbeitet.</p> <h4>Welche Rechte haben Sie bezüglich Ihrer Daten?</h4>
|
||||||
|
<p>Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer
|
||||||
|
gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung oder
|
||||||
|
Löschung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben,
|
||||||
|
können Sie diese Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht,
|
||||||
|
unter bestimmten Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu
|
||||||
|
verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde
|
||||||
|
zu.</p> <p>Hierzu sowie zu weiteren Fragen zum Thema Datenschutz kö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. a. um IP-Adressen, Kontaktanfragen, Meta- und Kommunikationsdaten, Vertragsdaten, Kontaktdaten,
|
||||||
|
Namen, Websitezugriffe und sonstige Daten, die über eine Website generiert werden, handeln.</p> <p>Das
|
||||||
|
externe Hosting erfolgt zum Zwecke der Vertragserfüllung gegenü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ßlich auf Grundlage von
|
||||||
|
Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TDDDG, soweit die Einwilligung die Speicherung von Cookies oder
|
||||||
|
den Zugriff auf Informationen im Endgerät des Nutzers (z. 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ü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 über Auftragsverarbeitung (AVV) zur Nutzung
|
||||||
|
des oben genannten Dienstes geschlossen. Hierbei handelt es sich um einen datenschutzrechtlich vorgeschriebenen
|
||||||
|
Vertrag, der gewä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­informationen</h2>
|
||||||
|
<h3>Datenschutz</h3> <p>Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr
|
||||||
|
ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen
|
||||||
|
Datenschutzvorschriften sowie dieser Datenschutzerklärung.</p> <p>Wenn Sie diese Website benutzen,
|
||||||
|
werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie
|
||||||
|
persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert,
|
||||||
|
welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das
|
||||||
|
geschieht.</p> <p>Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der
|
||||||
|
Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor
|
||||||
|
dem Zugriff durch Dritte ist nicht möglich.</p>
|
||||||
|
<h3>Hinweis zur verantwortlichen Stelle</h3> <p>Die verantwortliche Stelle fü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ürliche oder juristische Person, die allein oder gemeinsam mit anderen
|
||||||
|
über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z. B. Namen, E-Mail-
|
||||||
|
Adressen o. Ä.) entscheidet.</p>
|
||||||
|
<h3>Speicherdauer</h3> <p>Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer
|
||||||
|
genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung
|
||||||
|
entfällt. Wenn Sie ein berechtigtes Löschersuchen geltend machen oder eine Einwilligung zur
|
||||||
|
Datenverarbeitung widerrufen, werden Ihre Daten gelöscht, sofern wir keine anderen rechtlich
|
||||||
|
zulässigen Gründe für die Speicherung Ihrer personenbezogenen Daten haben (z. B. steuer-
|
||||||
|
oder handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die Löschung nach Fortfall
|
||||||
|
dieser Grü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ücklichen Einwilligung in die Übertragung personenbezogener
|
||||||
|
Daten in Drittstaaten erfolgt die Datenverarbeitung auß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ät (z. B. via
|
||||||
|
Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zusätzlich auf Grundlage von §
|
||||||
|
25 Abs. 1 TDDDG. Die Einwilligung ist jederzeit widerrufbar. Sind Ihre Daten zur Vertragserfüllung oder zur
|
||||||
|
Durchführung vorvertraglicher Maß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ü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. Über die
|
||||||
|
jeweils im Einzelfall einschlägigen Rechtsgrundlagen wird in den folgenden Absätzen dieser
|
||||||
|
Datenschutzerklärung informiert.</p>
|
||||||
|
<h3>Empfänger von personenbezogenen Daten</h3> <p>Im Rahmen unserer Geschäftstätigkeit
|
||||||
|
arbeiten wir mit verschiedenen externen Stellen zusammen. Dabei ist teilweise auch eine Ü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üllung erforderlich ist, wenn wir gesetzlich
|
||||||
|
hierzu verpflichtet sind (z. B. Weitergabe von Daten an Steuerbehö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ültigen Vertrags über Auftragsverarbeitung weiter. Im Falle einer
|
||||||
|
gemeinsamen Verarbeitung wird ein Vertrag über gemeinsame Verarbeitung geschlossen.</p>
|
||||||
|
<h3>Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3> <p>Viele Datenverarbeitungsvorgänge sind
|
||||||
|
nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung
|
||||||
|
jederzeit widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom
|
||||||
|
Widerruf unberührt.</p>
|
||||||
|
<h3>Widerspruchsrecht gegen die Datenerhebung in besonderen Fä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ÜNDEN, DIE SICH AUS IHRER
|
||||||
|
BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN
|
||||||
|
WIDERSPRUCH EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES
|
||||||
|
PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN
|
||||||
|
SIE DIESER DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE
|
||||||
|
BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR
|
||||||
|
KÖNNEN ZWINGENDE SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG
|
||||||
|
NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN ÜBERWIEGEN ODER DIE
|
||||||
|
VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG ODER VERTEIDIGUNG VON
|
||||||
|
RECHTSANSPRÜ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Ü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­recht bei der zuständigen Aufsichts­behörde</h3> <p>Im Falle von
|
||||||
|
Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer
|
||||||
|
Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthalts, ihres
|
||||||
|
Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu. Das Beschwerderecht besteht
|
||||||
|
unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.</p>
|
||||||
|
<h3>Recht auf Daten­übertrag­barkeit</h3> <p>Sie haben das Recht, Daten, die wir auf Grundlage
|
||||||
|
Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in
|
||||||
|
einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte
|
||||||
|
Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch
|
||||||
|
machbar ist.</p>
|
||||||
|
<h3>Auskunft, Berichtigung und Löschung</h3> <p>Sie haben im Rahmen der geltenden gesetzlichen
|
||||||
|
Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen
|
||||||
|
Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf
|
||||||
|
Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene
|
||||||
|
Daten können Sie sich jederzeit an uns wenden.</p>
|
||||||
|
<h3>Recht auf Einschränkung der Verarbeitung</h3> <p>Sie haben das Recht, die Einschränkung der
|
||||||
|
Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu können Sie sich jederzeit an uns wenden.
|
||||||
|
Das Recht auf Einschränkung der Verarbeitung besteht in folgenden Fällen:</p> <ul> <li>Wenn Sie die
|
||||||
|
Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, benötigen wir in der Regel Zeit,
|
||||||
|
um dies zu überprüfen. Für die Dauer der Prüfung haben Sie das Recht, die
|
||||||
|
Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn die
|
||||||
|
Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig geschah/geschieht, können Sie statt
|
||||||
|
der Löschung die Einschränkung der Datenverarbeitung verlangen.</li> <li>Wenn wir Ihre
|
||||||
|
personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur Ausübung, Verteidigung oder
|
||||||
|
Geltendmachung von Rechtsansprüchen benötigen, haben Sie das Recht, statt der Löschung die
|
||||||
|
Einschrä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ägung zwischen Ihren und unseren
|
||||||
|
Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen überwiegen, haben Sie das
|
||||||
|
Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> </ul>
|
||||||
|
<p>Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen diese
|
||||||
|
Daten – von ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur Geltendmachung,
|
||||||
|
Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen
|
||||||
|
natürlichen oder juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der
|
||||||
|
Europäischen Union oder eines Mitgliedstaats verarbeitet werden.</p>
|
||||||
|
|
||||||
<p>
|
<h4>Cookies</h4>
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
<p>
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
Unsere Internetseiten verwenden so genannte „Cookies“. Cookies sind kleine Datenpakete und richten auf
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
Ihrem Endgerät keinen Schaden an. Sie werden entweder vorübergehend für die Dauer einer Sitzung
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
(Session-Cookies) oder dauerhaft (permanente Cookies) auf Ihrem Endgerät gespeichert. Session-Cookies
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
werden nach Ende Ihres Besuchs automatisch gelöscht. Permanente Cookies bleiben auf Ihrem Endgerät
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
gespeichert, bis Sie diese selbst löschen oder eine automatische Löschung durch Ihren Webbrowser erfolgt.
|
||||||
</p>
|
Cookies haben verschiedene Funktionen. Zahlreiche Cookies sind technisch notwendig, da bestimmte
|
||||||
|
Webseitenfunktionen ohne diese nicht funktionieren würden (z. B. die Anmeldung). Andere Cookies können zur
|
||||||
|
Auswertung des Nutzerverhaltens oder zu Werbezwecken verwendet werden.
|
||||||
|
Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs, zur Bereitstellung
|
||||||
|
bestimmter, von Ihnen erwünschter Funktionen oder zur Optimierung der
|
||||||
|
Website (z. B. Cookies zur Messung des Webpublikums) erforderlich sind (notwendige Cookies), werden auf
|
||||||
|
Grundlage von Art. 6 Abs. 1 lit. f DSGVO gespeichert, sofern keine andere Rechtsgrundlage angegeben wird.
|
||||||
|
Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von notwendigen Cookies zur
|
||||||
|
technisch fehlerfreien und optimierten Bereitstellung seiner Dienste. Sofern eine Einwilligung zur
|
||||||
|
Speicherung von Cookies und vergleichbaren Wiedererkennungstechnologien abgefragt wurde, erfolgt die
|
||||||
|
Verarbeitung ausschließlich auf Grundlage dieser Einwilligung (Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1
|
||||||
|
TDDDG); die Einwilligung ist jederzeit widerrufbar.
|
||||||
|
Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und
|
||||||
|
Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen
|
||||||
|
sowie das automatische Löschen der Cookies beim Schließen des Browsers aktivieren. Bei der
|
||||||
|
Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.
|
||||||
|
Sofern weitere Cookies und Dienste auf dieser Website eingesetzt werden, können Sie dies dieser
|
||||||
|
Datenschutzerklärung entnehmen.
|
||||||
|
|
||||||
|
Auf dieser Webseite werden jedoch nur technisch notwendige Cookies verwendet.
|
||||||
|
</p>
|
||||||
|
<p>Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a></p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+26
-40
@@ -7,57 +7,43 @@ include_once 'php/controller/home-controller.php';
|
|||||||
Inhalt: Beinhaltet den Inhalt der Startseite
|
Inhalt: Beinhaltet den Inhalt der Startseite
|
||||||
-->
|
-->
|
||||||
<main>
|
<main>
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein Fehler beim Speichern aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "new_article"): ?>
|
|
||||||
<p class="alert-message is-success">
|
|
||||||
Dein Beitrag wurde erfolgreich veröffentlicht!
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Diese Kategorie ist nicht gültig.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php
|
|
||||||
unset($_SESSION["message"]);
|
|
||||||
?>
|
|
||||||
|
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<p>
|
<p>
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
Schön, dass du da bist. EduForge ist deine digitale Werkstatt für Lernerfolg. Wir unterstützen Schüler und
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
Studierende dabei, die passenden Lernmaterialien zu finden, Wissenslücken zu schließen und Gelerntes gezielt
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
zu vertiefen.
|
||||||
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>
|
||||||
|
|
||||||
<!-- Flexbox -->
|
<!-- Flexbox für aktive Kategorien -->
|
||||||
<div class="flexbox">
|
<div class="flexbox">
|
||||||
|
<?php
|
||||||
|
if (!empty($categoriesWithArticles) && is_array($categoriesWithArticles)):
|
||||||
|
foreach ($categoriesWithArticles as $category):
|
||||||
|
?>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="index.php?pfad=showCategory&category=informatik" class="category-link">Informatik</a>
|
<a href="index.php?pfad=showCategory&category=<?php echo htmlspecialchars($category['slug']); ?>" class="category-link">
|
||||||
<div class="article-link">
|
<?php echo htmlspecialchars($category['name']); ?>
|
||||||
<a href="index.php?pfad=showArticle&id=<?php echo $dummy3->getId()?>"><?php if(isset($dummy3)){echo $dummy3->getTitle();}else{echo "Fehler: Beitrag nicht gefunden!";} ?></a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
<!-- die 5 neuesten Beiträge der Kategorie -->
|
||||||
<a href="index.php?pfad=showCategory&category=mathe" class="category-link">Mathe</a>
|
<div class="article-links-wrapper">
|
||||||
|
<?php foreach ($category['articles'] as $article): ?>
|
||||||
<div class="article-link">
|
<div class="article-link">
|
||||||
<a href="index.php?pfad=showArticle&id=<?php echo $dummy1->getId()?>"><?php if(isset($dummy1)){echo $dummy1->getTitle();}else{echo "Fehler: Beitrag nicht gefunden!";} ?></a>
|
<a href="index.php?pfad=showArticle&id=<?php echo htmlspecialchars($article->getId()); ?>">
|
||||||
</div>
|
<?php echo htmlspecialchars($article->getTitle()); ?>
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<?php endforeach; ?>
|
||||||
<a href="index.php?pfad=showCategory&category=physik" class="category-link">Physik</a>
|
|
||||||
<div class="article-link">
|
|
||||||
<a href="index.php?pfad=showArticle&id=<?php echo $dummy2->getId()?>"><?php if(isset($dummy2)){echo $dummy2->getTitle();}else{echo "Fehler: Beitrag nicht gefunden!";} ?></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
else:
|
||||||
|
?>
|
||||||
|
<p>Aktuell sind keine Beiträge in den Kategorien vorhanden.</p>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
@@ -6,14 +6,12 @@
|
|||||||
<main>
|
<main>
|
||||||
|
|
||||||
<h1>Impressum</h1>
|
<h1>Impressum</h1>
|
||||||
|
<p>Gruppe C<br />
|
||||||
<p>
|
Ammerlaender Heerstrasse, 114-118<br />
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
26129 Oldenburg</p>
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
<h2>Kontakt</h2>
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
<p>Telefon: 0123456789<br />
|
||||||
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
E-Mail: GruppeC@uni-oldenburg.de</p>
|
||||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
|
<p>Quelle: <a href="https://www.e-recht24.de/impressum-generator.html">https://www.e-recht24.de/impressum-generator.html</a></p>
|
||||||
sea takimata sanctus est Lorem ipsum dolor sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Informatik
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Informatik</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>
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="datenschutzVSdatensicherheit.php">Datenschutz vs. Datensicherheit</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
+10
-2
@@ -11,8 +11,10 @@ $error = $error ?? null;
|
|||||||
|
|
||||||
<h1>Bitte anmelden</h1>
|
<h1>Bitte anmelden</h1>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php include_once "includes/alertMessages.php"; ?>
|
||||||
<p style="color:red;">
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
<?php echo htmlspecialchars($error); ?>
|
<?php echo htmlspecialchars($error); ?>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -41,6 +43,12 @@ $error = $error ?? null;
|
|||||||
anmelden
|
anmelden
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="register-link">
|
||||||
|
<a href="index.php?pfad=password-forgotten">
|
||||||
|
Passwort vergessen?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="register-link">
|
<div class="register-link">
|
||||||
<a href="index.php?pfad=register">
|
<a href="index.php?pfad=register">
|
||||||
Noch keinen Account? Jetzt hier registrieren!
|
Noch keinen Account? Jetzt hier registrieren!
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Mathe
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Mathe</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>
|
|
||||||
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="pythagoras.php">Satz des Pythagoras</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
@@ -23,19 +23,58 @@
|
|||||||
include_once 'includes/navbar.php';
|
include_once 'includes/navbar.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
<h1>Nutzungsbedingungen</h1>
|
<h1>Nutzungsbedingungen</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
|
Diese Nutzungsbedingungen regeln die Nutzung der Plattform EduForge.
|
||||||
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
Mit der Registrierung und Nutzung der Website erkennen Nutzerinnen und Nutzer
|
||||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
|
die nachfolgenden Bedingungen an.
|
||||||
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>
|
||||||
|
|
||||||
|
<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>
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
$error = $error ?? null;
|
||||||
|
$success = $success ?? null;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
|
||||||
|
<h1>Passwort vergessen</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success)): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
<?php echo $success; ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="index.php?pfad=password-forgotten">
|
||||||
|
|
||||||
|
<p class="input-label">E-Mail-Adresse:</p>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="E-Mail-Adresse"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<p class="input-label">Neues Passwort:</p>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
class="login-input"
|
||||||
|
placeholder="Neues Passwort"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
name="passwordForgottenSubmit"
|
||||||
|
class="button">
|
||||||
|
Passwort zurücksetzen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="register-link">
|
||||||
|
<a href="index.php?pfad=login">
|
||||||
|
Zurück zum Login
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<!--
|
|
||||||
Seite: Physik
|
|
||||||
Inhalt: Eine Übersicht über alle Beiträge zum Fach
|
|
||||||
-->
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h1>Physik</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>
|
|
||||||
|
|
||||||
<h2>Artikel:</h2>
|
|
||||||
<div>
|
|
||||||
<a href="tunneleffekt.php">Der Tunneleffekt</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
+58
-14
@@ -7,8 +7,8 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
|
|
||||||
<main class="form-page">
|
<main class="form-page">
|
||||||
<div class="flexbox">
|
<div class="flexbox">
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
<?php if (!empty($error)): ?>
|
<?php if (!empty($error)): ?>
|
||||||
<p class="alert-message is-error">
|
<p class="alert-message is-error">
|
||||||
@@ -81,21 +81,18 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<a href="index.php?pfad=deleteAccount" class="button">
|
<form action="php/controller/deleteAccount-controller.php" method="POST">
|
||||||
|
<button type="submit"
|
||||||
|
class="button"
|
||||||
|
onclick="return confirm('Möchtest du deinen Account wirklich unwiderruflich löschen?');">
|
||||||
Account löschen
|
Account löschen
|
||||||
</a>
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<a href="index.php?pfad=logout" class="button">
|
|
||||||
Abmelden
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<?php include_once "includes/alertMessages.php"?>
|
|
||||||
|
|
||||||
<h2 class="section-title">Meine Beiträge</h2>
|
<h2 class="section-title">Meine Beiträge</h2>
|
||||||
|
|
||||||
<div class="articles-list">
|
<div class="articles-list">
|
||||||
@@ -120,7 +117,9 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="article-title">
|
<h3 class="article-title">
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo $userArticle->getID(); ?>">
|
||||||
<?php echo htmlspecialchars($userArticle->getTitle()); ?>
|
<?php echo htmlspecialchars($userArticle->getTitle()); ?>
|
||||||
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
@@ -156,7 +155,9 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<form action="php/controller/deleteArticle-controller.php" method="POST">
|
<form action="php/controller/deleteArticle-controller.php" method="POST">
|
||||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($userArticle->getID()); ?>">
|
<input type="hidden"
|
||||||
|
name="id"
|
||||||
|
value="<?php echo htmlspecialchars($userArticle->getID()); ?>">
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="button"
|
class="button"
|
||||||
@@ -164,7 +165,6 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
@@ -179,10 +179,54 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="comments-section">
|
||||||
|
|
||||||
|
<h2 class="section-title">Meine Kommentare</h2>
|
||||||
|
|
||||||
|
<!-- Liste aller Kommentare des Nutzers -->
|
||||||
|
<div class="comments-list">
|
||||||
|
|
||||||
|
<?php if (isset($userComments) && count($userComments) > 0): ?>
|
||||||
|
|
||||||
|
<?php foreach ($userComments as $comment): ?>
|
||||||
|
<div class="article-item">
|
||||||
|
|
||||||
|
<!-- Erstellungsdatum des Kommentars -->
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="article-date">
|
||||||
|
<?php echo htmlspecialchars($comment->getCreated()); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inhalt des Kommentars -->
|
||||||
|
<p>
|
||||||
|
<?php echo nl2br(htmlspecialchars($comment->getContent())); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Link zum Beitrag, unter dem der Kommentar geschrieben wurde -->
|
||||||
|
<a href="index.php?pfad=showArticle&id=<?php echo htmlspecialchars($comment->getArticleId()); ?>"
|
||||||
|
class="edit-link-button">
|
||||||
|
Zum Beitrag
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<p>Du hast noch keine Kommentare geschrieben.</p>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<?php unset($_SESSION["message"]); ?>
|
<?php unset($_SESSION["message"]); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
$error = $error ?? null;
|
$error = $error ?? null;
|
||||||
|
$success = $success ?? null;
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -12,11 +13,17 @@ $error = $error ?? null;
|
|||||||
<h1>Jetzt Registrieren!</h1>
|
<h1>Jetzt Registrieren!</h1>
|
||||||
|
|
||||||
<?php if (!empty($error)): ?>
|
<?php if (!empty($error)): ?>
|
||||||
<p class="alert-message is-error" style="color:red;">
|
<p class="alert-message is-error">
|
||||||
<?php echo htmlspecialchars($error); ?>
|
<?php echo htmlspecialchars($error); ?>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success)): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
<?php echo $success; ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="post" action="index.php?pfad=register">
|
<form method="post" action="index.php?pfad=register">
|
||||||
|
|
||||||
<p class="input-label">Email:</p>
|
<p class="input-label">Email:</p>
|
||||||
|
|||||||
+132
-36
@@ -1,53 +1,131 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/../includes/resultsHelper.php';
|
||||||
|
|
||||||
$results = $_SESSION["search_results"] ?? [];
|
$rawResults = $_SESSION["search_results"] ?? [];
|
||||||
$query = $_SESSION["search_query"] ?? "";
|
$query = $_SESSION["search_query"] ?? "";
|
||||||
|
|
||||||
|
// Sortierung / Kategorie / Limit: GET-Parameter haben Vorrang, sonst
|
||||||
|
// Rückfall auf die zuletzt in der Session gemerkten Werte. So bleibt
|
||||||
|
// z.B. ein reiner Pagination-Link (?page=2) bei der aktuellen
|
||||||
|
// Sortierung/Filterung.
|
||||||
|
$currentSort = $_GET['sort'] ?? ($_SESSION['search_sort'] ?? 'alphabet');
|
||||||
|
if (!in_array($currentSort, ['alphabet', 'likes', 'newest', 'oldest'])) {
|
||||||
|
$currentSort = 'alphabet';
|
||||||
|
}
|
||||||
|
$_SESSION['search_sort'] = $currentSort;
|
||||||
|
|
||||||
|
$currentCategory = strtolower($_GET['category'] ?? ($_SESSION['search_category'] ?? 'all'));
|
||||||
|
$_SESSION['search_category'] = $currentCategory;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : ($_SESSION['search_limit'] ?? 10);
|
||||||
|
if (!in_array($limit, [10, 20, 50, 100])) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['search_limit'] = $limit;
|
||||||
|
|
||||||
|
// Serverseitig sortieren + filtern
|
||||||
|
$filteredResults = sortSearchResults($rawResults, $currentSort);
|
||||||
|
$filteredResults = filterSearchResultsByCategory($filteredResults, $currentCategory);
|
||||||
|
|
||||||
|
$totalResultsCount = count($filteredResults);
|
||||||
|
$totalPages = max(1, (int)ceil($totalResultsCount / $limit));
|
||||||
|
|
||||||
|
$currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
if ($currentPage < 1) {
|
||||||
|
$currentPage = 1;
|
||||||
|
} elseif ($currentPage > $totalPages) {
|
||||||
|
$currentPage = $totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = ($currentPage - 1) * $limit;
|
||||||
|
|
||||||
|
// Dies ist die No-JS-Basisversion: nur die aktuelle Seite wird gerendert.
|
||||||
|
// Bei aktiviertem JS lädt js/results-app.js zusätzlich den kompletten
|
||||||
|
// Datensatz nach (ein einziger Request) und übernimmt Sortierung, Filter
|
||||||
|
// und Pagination danach komplett im Browser, ohne weitere Serveranfragen.
|
||||||
|
$results = array_slice($filteredResults, $offset, $limit);
|
||||||
$resultCount = count($results);
|
$resultCount = count($results);
|
||||||
|
|
||||||
|
// Kategorien für das Auswahlfeld (Wert => Label)
|
||||||
|
$categories = [
|
||||||
|
'Deutsch' => 'Deutsch', 'Englisch' => 'Englisch', 'Franzoesisch' => 'Französisch',
|
||||||
|
'Latein' => 'Latein', 'Literatur' => 'Literatur', 'Mathematik' => 'Mathematik',
|
||||||
|
'Biologie' => 'Biologie', 'Informatik' => 'Informatik', 'Chemie' => 'Chemie',
|
||||||
|
'Physik' => 'Physik', 'Astronomie' => 'Astronomie', 'Geschichte' => 'Geschichte',
|
||||||
|
'Erdkunde' => 'Erdkunde', 'Sozialkunde' => 'Sozialkunde', 'Wirtschaftskunde' => 'Wirtschaftskunde',
|
||||||
|
'Religion' => 'Religion', 'Ethikunterricht' => 'Ethikunterricht', 'Philosophie' => 'Philosophie',
|
||||||
|
'Psychologie' => 'Psychologie', 'Kunst' => 'Kunst', 'Musik' => 'Musik', 'Theater' => 'Theater',
|
||||||
|
'Technik' => 'Technik', 'Werken' => 'Werken', 'Hauswirtschaft' => 'Hauswirtschaft', 'Sport' => 'Sport',
|
||||||
|
];
|
||||||
?>
|
?>
|
||||||
<!--
|
<!--
|
||||||
Seite: Suchergebnisse
|
Seite: Suchergebnisse
|
||||||
Inhalt: Zeigt die Ergebnisse einer Suche an
|
Inhalt: Zeigt die Ergebnisse einer Suche an.
|
||||||
|
Funktioniert ohne JavaScript (serverseitige Sortierung/Filter/Pagination
|
||||||
|
über echte Links + Formulare) und wird bei aktiviertem JS von
|
||||||
|
js/search-results.js vollständig client-seitig übernommen.
|
||||||
-->
|
-->
|
||||||
<div class="s-res-layout-grid">
|
<div class="s-res-layout-grid">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
<!-- Links: Seitenleiste für Filter und Suche -->
|
<!-- Links: Seitenleiste für Filter und Suche -->
|
||||||
<aside class="s-res-sidebar">
|
<aside class="s-res-sidebar">
|
||||||
|
|
||||||
<!-- Sortierfuntion Box und Such Box-->
|
<!-- Formular 1: startet eine NEUE Suche (löst immer einen Server-Request aus) -->
|
||||||
<form action="php/controller/search-results-controller.php" method="GET" class="s-res-sidebar-form">
|
<form action="php/controller/search-results-controller.php" method="GET" id="search-form-id" class="s-res-sidebar-form">
|
||||||
|
|
||||||
<div class="s-res-sidebar-box">
|
<div class="s-res-sidebar-box">
|
||||||
<h3 class="s-res-sidebar-title">Suche anpassen</h3>
|
<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>
|
<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>
|
<button type="submit" class="nav__search-button">Suchen</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Formular 2: sortiert/filtert die BEREITS gefundenen Ergebnisse.
|
||||||
|
Ohne JS: echter Reload dieser Seite, aber ohne erneute Datenbank-Suche.
|
||||||
|
Mit JS: search-results.js übernimmt das live im Browser, der Submit-Button
|
||||||
|
wird dafür nicht benötigt und über <noscript> versteckt. -->
|
||||||
|
<form action="index.php" method="GET" id="s-res-filter-form" class="s-res-sidebar-form">
|
||||||
|
<input type="hidden" name="pfad" value="search-results">
|
||||||
|
<input type="hidden" name="q" value="<?php echo htmlspecialchars($query); ?>">
|
||||||
|
|
||||||
<div class="s-res-sidebar-box">
|
<div class="s-res-sidebar-box">
|
||||||
<h3 class="s-res-sidebar-title">Sortierung</h3>
|
<h3 class="s-res-sidebar-title">Sortierung</h3>
|
||||||
<?php $currentSort = $_SESSION['search_sort'] ?? 'alphabet'; ?>
|
|
||||||
<div class="s-res-filter-group">
|
<div class="s-res-filter-group">
|
||||||
<label class="s-res-filter-option">
|
<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>
|
<span>Alphabetisch</span>
|
||||||
</label>
|
</label>
|
||||||
<!-- Noch disabled, da likes noch nicht implementiert-->
|
|
||||||
<label class="s-res-filter-option">
|
<label class="s-res-filter-option">
|
||||||
<input type="radio" name="sort" value="likes" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?> disabled>
|
<input type="radio" name="sort" value="likes" class="sort-radio" <?php echo $currentSort === 'likes' ? 'checked' : ''; ?>>
|
||||||
<span style="color: #94a3b8;">Beliebtheit (Likes)</span>
|
<span>Beliebtheit (Likes)</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="s-res-filter-option">
|
<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>
|
<span>Neueste Beiträge</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="s-res-filter-option">
|
<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>
|
<span>Älteste Beiträge</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Kategorie filtern</h3>
|
||||||
|
<select id="category-filter" name="category" class="s-res-limit-select" style="width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #cbd5e1;">
|
||||||
|
<option value="all" <?php echo $currentCategory === 'all' ? 'selected' : ''; ?>>Alle Kategorien</option>
|
||||||
|
<?php foreach ($categories as $value => $label): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($value); ?>" <?php echo $currentCategory === strtolower($value) ? 'selected' : ''; ?>><?php echo htmlspecialchars($label); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="nav__search-button">Filter anwenden</button>
|
||||||
|
</noscript>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
@@ -56,59 +134,77 @@ $resultCount = count($results);
|
|||||||
|
|
||||||
<div class="s-res-header">
|
<div class="s-res-header">
|
||||||
<h1 class="s-res-main-title">Suchergebnisse</h1>
|
<h1 class="s-res-main-title">Suchergebnisse</h1>
|
||||||
<p class="s-res-meta"><?php echo $resultCount; ?> Treffer für Ihre Suchanfrage "<?php echo htmlspecialchars($query); ?>"</p>
|
<p class="s-res-meta"><span id="s-res-result-count"><?php echo $totalResultsCount; ?></span> Treffer für Ihre Suchanfrage "<?php echo htmlspecialchars($query); ?>"</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ergebnisliste -->
|
<!-- Ergebnisliste -->
|
||||||
<div class="s-res-list">
|
<div class="s-res-list">
|
||||||
<?php
|
<?php if (!empty($results)): ?>
|
||||||
if (!empty($results)): ?>
|
|
||||||
|
|
||||||
<?php foreach ($results as $item): ?>
|
<?php foreach ($results as $item):
|
||||||
<div class="s-res-item">
|
$likesCount = getLikeCount($item);
|
||||||
|
?>
|
||||||
|
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
|
||||||
<div class="s-res-content">
|
<div class="s-res-content">
|
||||||
<h2 class="s-res-item-title">
|
<h2 class="s-res-item-title">
|
||||||
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
|
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
|
||||||
<?php echo htmlspecialchars($item['title']); ?>
|
<?php echo htmlspecialchars($item['title']); ?>
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
<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>
|
<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>
|
||||||
<div class="s-res-arrow">→</div>
|
<div class="s-res-arrow">→</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php
|
<?php elseif (isset($_SESSION["message"]) && $_SESSION["message"] === "invalid_search_query"): ?>
|
||||||
elseif (isset($_SESSION["search_query"]) && $_SESSION["search_query"] !== "" && $resultCount === 0): ?>
|
<p>Unzulässige Suchanfrage</p>
|
||||||
|
|
||||||
|
<?php elseif ($query !== "" && $totalResultsCount === 0 && count($rawResults) > 0): ?>
|
||||||
|
<p>Keine Beiträge in dieser Kategorie gefunden.</p>
|
||||||
|
|
||||||
|
<?php elseif ($query !== "" && $totalResultsCount === 0): ?>
|
||||||
<p>Keine Beiträge zu diesem Suchbegriff gefunden.</p>
|
<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>
|
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php
|
<?php unset($_SESSION["message"]); ?>
|
||||||
unset($_SESSION["message"]);
|
|
||||||
?>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="s-res-pagination-footer">
|
<div class="s-res-pagination-footer">
|
||||||
|
|
||||||
<!-- Auswahl der Ergebnisse pro Seite -->
|
<!-- Auswahl der Ergebnisse pro Seite. Gehört per form="..." logisch
|
||||||
|
zum Filterformular oben -->
|
||||||
<div class="s-res-limit-selector">
|
<div class="s-res-limit-selector">
|
||||||
<label for="s-res-per-page" class="s-res-limit-label">Ergebnisse pro Seite:</label>
|
<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">
|
<select id="s-res-per-page" name="limit" form="s-res-filter-form" class="s-res-limit-select">
|
||||||
<option value="10" selected>10</option>
|
<option value="10" <?php echo $limit === 10 ? 'selected' : ''; ?>>10</option>
|
||||||
<option value="20">20</option>
|
<option value="20" <?php echo $limit === 20 ? 'selected' : ''; ?>>20</option>
|
||||||
<option value="50">50</option>
|
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>50</option>
|
||||||
<option value="100">100</option>
|
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>100</option>
|
||||||
</select>
|
</select>
|
||||||
|
<noscript><button type="submit" form="s-res-filter-form" class="nav__search-button">Übernehmen</button></noscript>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="s-res-page-navigation">
|
<nav class="s-res-page-navigation" aria-label="Seitennavigation">
|
||||||
<button type="button" class="s-res-page-btn" disabled>«</button>
|
<!-- No-JS-Fallback: -->
|
||||||
<button type="button" class="s-res-page-btn s-res-page-btn-active">1</button>
|
<noscript>
|
||||||
<button type="button" class="s-res-page-btn">»</button>
|
<?php echo renderNoJsPagination($currentPage, $totalPages, $query, $currentSort, $currentCategory, $limit); ?>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- JS-Version: wird per search-results.js befüllt/eingeblendet: -->
|
||||||
|
<div id="js-page-navigation" style="display:none;">
|
||||||
|
<button type="button" class="s-res-page-btn" id="prev-page-btn">«</button>
|
||||||
|
<span id="dynamic-page-numbers"></span>
|
||||||
|
<button type="button" class="s-res-page-btn" id="next-page-btn">»</button>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zeigt den Inhalt einer simulierten E-Mail an.
|
||||||
|
* Die Datei wird über einen zufällig erzeugten Token geladen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = __DIR__ . "/../data/mails/" . $token . ".html";
|
||||||
|
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
echo "<p>Datei nicht gefunden.</p>";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
<h1>Simulierte E-Mail</h1>
|
||||||
|
|
||||||
|
<?php include $file; ?>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
+131
-31
@@ -1,51 +1,52 @@
|
|||||||
<?php
|
<?php
|
||||||
|
$comments = [];
|
||||||
|
$mainComments = [];
|
||||||
|
$repliesByParent = [];
|
||||||
|
$articleObj = null;
|
||||||
|
|
||||||
include_once 'php/controller/showArticle-controller.php';
|
include_once 'php/controller/showArticle-controller.php';
|
||||||
?>
|
?>
|
||||||
<!--
|
<!--
|
||||||
Seite: Anzeige für Beiträge
|
Seite: Anzeige für Beiträge
|
||||||
Funktion: Stellt einen übergebenen Beitrag dar.
|
Funktion: Stellt einen übergebenen Beitrag dar.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Hauptcontainer für die Beitragsansicht (Ausschließlich der Content-Bereich) -->
|
<!-- Hauptcontainer für die Beitragsansicht (Ausschließlich der Content-Bereich) -->
|
||||||
<main class="article-view-container">
|
<main class="article-view-container">
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein interner Fehler aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_id"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein Fehler aufgetreten. Die ID konnte nicht ausgelesen werden. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_parameters"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Jeder Beitrag muss einen Titel, Kategorie und Inhalt besitzen.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "article_updated"): ?>
|
|
||||||
<p class="alert-message is-success">
|
|
||||||
Dein Beitrag wurde erfolgreich bearbeitet!
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php
|
|
||||||
unset($_SESSION["message"]);
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!-- Metadaten & Titel -->
|
<!-- Metadaten & Titel -->
|
||||||
<div class="article-view-top-section">
|
<div class="article-view-top-section">
|
||||||
|
|
||||||
|
<div class="article-view-top-section">
|
||||||
|
|
||||||
|
<div class="category-and-likes-row">
|
||||||
<?php if (isset($category) && !empty($category)): ?>
|
<?php if (isset($category) && !empty($category)): ?>
|
||||||
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
|
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
|
||||||
<?php endif; ?>
|
<?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">
|
<h1 class="article-view-title">
|
||||||
<?php if (isset($title)) { echo htmlspecialchars($title); } ?>
|
<?php if (isset($title)) { echo htmlspecialchars($title); } ?>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="article-view-meta">
|
<div class="article-view-meta">
|
||||||
<?php if (isset($author) && !empty($author)): ?>
|
<?php if (isset($name) && !empty($name)): ?>
|
||||||
<span class="article-view-author">Von: <strong><?php echo htmlspecialchars($author); ?></strong></span>
|
<span class="article-view-author">Von: <strong><?php echo htmlspecialchars($name); ?></strong></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,8 +55,33 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
<!-- Beitrags-Inhalt -->
|
<!-- Beitrags-Inhalt -->
|
||||||
<div class="article-view-content">
|
<div class="article-view-content">
|
||||||
<?php if (isset($content)): ?>
|
<?php if (isset($content)): ?>
|
||||||
<!-- nl2br für Zeilenumbrüche -->
|
<?php
|
||||||
<div class="article-view-body"><?php echo nl2br(htmlspecialchars($content)); ?></div>
|
// Versuchen, den Inhalt von JSON in ein PHP-Array umzuwandeln
|
||||||
|
$blocks = json_decode($content, true);
|
||||||
|
|
||||||
|
// Wenn das JSON valide ist und Blöcke enthält
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($blocks)):
|
||||||
|
foreach ($blocks as $block):
|
||||||
|
if (isset($block['type']) && isset($block['value'])):
|
||||||
|
if ($block['type'] === 'text'): ?>
|
||||||
|
<!-- Textblock mit XSS-Schutz und Erhalt von Zeilenumbrüchen -->
|
||||||
|
<div class="article-view-body block-text">
|
||||||
|
<?php echo nl2br(htmlspecialchars($block['value'])); ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($block['type'] === 'image'): ?>
|
||||||
|
<!-- Bildblock, der auf den relativen Pfad im uploads-Ordner verweist -->
|
||||||
|
<div class="article-view-body block-image">
|
||||||
|
<img src="<?php echo htmlspecialchars($block['value']); ?>" alt="Beitragsbild">
|
||||||
|
</div>
|
||||||
|
<?php endif;
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
else: ?>
|
||||||
|
<!-- Fallback: Wenn der Beitrag alten Reintext aus der DB enthält -->
|
||||||
|
<div class="article-view-body block-text">
|
||||||
|
<?php echo nl2br(htmlspecialchars($content)); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -80,5 +106,79 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
</main>
|
<section class="article-comments-section">
|
||||||
|
<h2>Kommentare</h2>
|
||||||
|
|
||||||
|
<div id="comments-list">
|
||||||
|
<?php if (!empty($mainComments)): ?>
|
||||||
|
<?php foreach ($mainComments as $comment): ?>
|
||||||
|
<div class="comment-item" data-comment-id="<?php echo htmlspecialchars($comment->getId()); ?>">
|
||||||
|
<p>
|
||||||
|
<strong><?php echo htmlspecialchars($comment->getAuthor()); ?></strong>
|
||||||
|
<span><?php echo htmlspecialchars($comment->getCreated()); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($comment->getContent())); ?></p>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["user_email"])): ?>
|
||||||
|
<button type="button"
|
||||||
|
class="reply-button"
|
||||||
|
data-comment-id="<?php echo htmlspecialchars($comment->getId()); ?>"
|
||||||
|
data-author="<?php echo htmlspecialchars($comment->getAuthor()); ?>">
|
||||||
|
Antworten
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="comment-replies">
|
||||||
|
<?php if (isset($repliesByParent[$comment->getId()])): ?>
|
||||||
|
<?php foreach ($repliesByParent[$comment->getId()] as $reply): ?>
|
||||||
|
<div class="comment-item comment-reply">
|
||||||
|
<p>
|
||||||
|
<strong><?php echo htmlspecialchars($reply->getAuthor()); ?></strong>
|
||||||
|
<span><?php echo htmlspecialchars($reply->getCreated()); ?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($reply->getContent())); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="no-comments-message">
|
||||||
|
Noch keine Kommentare vorhanden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["user_email"])): ?>
|
||||||
|
<form id="comment-form">
|
||||||
|
<input type="hidden"
|
||||||
|
name="article_id"
|
||||||
|
value="<?php echo htmlspecialchars($_GET["id"] ?? ""); ?>">
|
||||||
|
|
||||||
|
<input type="hidden"
|
||||||
|
name="parent_comment_id"
|
||||||
|
id="parent-comment-id"
|
||||||
|
value="">
|
||||||
|
|
||||||
|
<p id="reply-info" class="reply-info" style="display: none;"></p>
|
||||||
|
|
||||||
|
<textarea name="content"
|
||||||
|
id="comment-content"
|
||||||
|
placeholder="Schreibe einen Kommentar..."
|
||||||
|
required></textarea>
|
||||||
|
|
||||||
|
<button type="submit" class="button">
|
||||||
|
Kommentar senden
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="comment-login-hint">
|
||||||
|
<p>Melde dich an, um einen Kommentar zu schreiben.</p>
|
||||||
|
<a href="index.php?pfad=login" class="button">Jetzt anmelden</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
+166
-13
@@ -1,31 +1,184 @@
|
|||||||
<?php
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
include_once "php/controller/showCategory-controller.php";
|
include_once "php/controller/showCategory-controller.php";
|
||||||
|
require_once __DIR__ . '/../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['category_results'] ?? [];
|
||||||
|
$category = $_SESSION['category_name'] ?? ($_GET['category'] ?? '');
|
||||||
|
|
||||||
|
// Sortierung / Suche / Limit: GET-Parameter haben Vorrang, sonst Rückfall auf
|
||||||
|
// die zuletzt in der Session gemerkten Werte (analog zu search-results.php).
|
||||||
|
$currentSort = $_GET['sort'] ?? ($_SESSION['cat_sort'] ?? 'alphabet');
|
||||||
|
if (!in_array($currentSort, ['alphabet', 'likes', 'newest', 'oldest'])) {
|
||||||
|
$currentSort = 'alphabet';
|
||||||
|
}
|
||||||
|
$_SESSION['cat_sort'] = $currentSort;
|
||||||
|
|
||||||
|
$query = trim($_GET['q'] ?? ($_SESSION['cat_query'] ?? ''));
|
||||||
|
$_SESSION['cat_query'] = $query;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : ($_SESSION['cat_limit'] ?? 10);
|
||||||
|
if (!in_array($limit, [10, 20, 50, 100])) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['cat_limit'] = $limit;
|
||||||
|
|
||||||
|
// Serverseitig sortieren + nach Suchbegriff filtern (Kategorie steht bereits fest)
|
||||||
|
$filteredResults = sortSearchResults($rawResults, $currentSort);
|
||||||
|
$filteredResults = filterResultsByQuery($filteredResults, $query);
|
||||||
|
|
||||||
|
$totalResultsCount = count($filteredResults);
|
||||||
|
$totalPages = max(1, (int)ceil($totalResultsCount / $limit));
|
||||||
|
|
||||||
|
$currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
if ($currentPage < 1) {
|
||||||
|
$currentPage = 1;
|
||||||
|
} elseif ($currentPage > $totalPages) {
|
||||||
|
$currentPage = $totalPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = ($currentPage - 1) * $limit;
|
||||||
|
|
||||||
|
// Dies ist die No-JS-Basisversion: nur die aktuelle Seite wird gerendert.
|
||||||
|
// Bei aktiviertem JS lädt js/showCategory.js zusätzlich den kompletten
|
||||||
|
// Kategorie-Datensatz nach (ein einziger Request) und übernimmt Sortierung,
|
||||||
|
// Suche und Pagination danach komplett im Browser, ohne weitere Serveranfragen.
|
||||||
|
$results = array_slice($filteredResults, $offset, $limit);
|
||||||
|
$resultCount = count($results);
|
||||||
?>
|
?>
|
||||||
|
<!--
|
||||||
|
Seite: Kategorie-Ansicht
|
||||||
|
Inhalt: Zeigt alle Beiträge einer Kategorie an, sortierbar und innerhalb
|
||||||
|
der Kategorie durchsuchbar.
|
||||||
|
Funktioniert ohne JavaScript (serverseitige Sortierung/Suche/Pagination
|
||||||
|
über echte Links + Formulare) und wird bei aktiviertem JS von
|
||||||
|
js/showCategory.js vollständig client-seitig übernommen.
|
||||||
|
-->
|
||||||
|
<div class="s-res-layout-grid">
|
||||||
|
<?php include_once "includes/alertMessages.php" ?>
|
||||||
|
|
||||||
<main>
|
<!-- Links: Seitenleiste für Suche (innerhalb der Kategorie) und Sortierung -->
|
||||||
|
<aside class="s-res-sidebar">
|
||||||
|
|
||||||
<h1><?php if (isset($category) && !empty($category)){ echo htmlspecialchars($category); } ?></h1>
|
<!-- Sortiert/durchsucht die BEREITS geladenen Kategorie-Beiträge.
|
||||||
|
Ohne JS: echter Reload dieser Seite, aber ohne erneute Datenbank-Abfrage.
|
||||||
|
Mit JS: js/showCategory.js übernimmt das live im Browser, der Submit-Button
|
||||||
|
wird dafür nicht benötigt und über <noscript> versteckt. -->
|
||||||
|
<form action="index.php" method="GET" id="cat-filter-form" class="s-res-sidebar-form">
|
||||||
|
<input type="hidden" name="pfad" value="showCategory">
|
||||||
|
<input type="hidden" name="category" value="<?php echo htmlspecialchars($category); ?>">
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">In dieser Kategorie suchen</h3>
|
||||||
|
<input type="search" id="cat-search-input" name="q" placeholder="Suchen..." class="nav__search" value="<?php echo htmlspecialchars($query); ?>" maxlength="50">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-res-sidebar-box">
|
||||||
|
<h3 class="s-res-sidebar-title">Sortierung</h3>
|
||||||
|
<div class="s-res-filter-group">
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<input type="radio" name="sort" value="alphabet" class="sort-radio" <?php echo $currentSort === 'alphabet' ? 'checked' : ''; ?>>
|
||||||
|
<span>Alphabetisch</span>
|
||||||
|
</label>
|
||||||
|
<label class="s-res-filter-option">
|
||||||
|
<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" 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" class="sort-radio" <?php echo $currentSort === 'oldest' ? 'checked' : ''; ?>>
|
||||||
|
<span>Älteste Beiträge</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<button type="submit" class="nav__search-button">Anwenden</button>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="s-res-main-content">
|
||||||
|
|
||||||
|
<div class="s-res-header">
|
||||||
|
<h1 class="s-res-main-title">Kategorie: <?php echo htmlspecialchars($category); ?></h1>
|
||||||
|
<p class="s-res-meta">
|
||||||
|
<span id="s-res-result-count"><?php echo $totalResultsCount; ?></span> Treffer in dieser Kategorie<?php echo $query !== '' ? ' für "' . htmlspecialchars($query) . '"' : ''; ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnisliste -->
|
||||||
<div class="s-res-list">
|
<div class="s-res-list">
|
||||||
<?php
|
<?php if (!empty($results)): ?>
|
||||||
if (!empty($articles)): ?>
|
|
||||||
<?php foreach ($articles as $article): ?>
|
<?php foreach ($results as $item):
|
||||||
<div class="s-res-item">
|
$likesCount = getLikeCount($item);
|
||||||
|
?>
|
||||||
|
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
|
||||||
<div class="s-res-content">
|
<div class="s-res-content">
|
||||||
<h2 class="s-res-item-title">
|
<h2 class="s-res-item-title">
|
||||||
<a href="index.php?pfad=showArticle&id=<?php echo $article->getId(); ?>" class="s-res-link">
|
<a href="index.php?pfad=showArticle&id=<?php echo $item['id']; ?>" class="s-res-link">
|
||||||
<?php echo htmlspecialchars($article->getTitle()); ?>
|
<?php echo htmlspecialchars($item['title']); ?>
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="s-res-author">Von: <span class="s-res-author-name"><?php echo htmlspecialchars($article->getAuthor()); ?></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>
|
||||||
<div class="s-res-arrow">→</div>
|
<div class="s-res-arrow">→</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php
|
|
||||||
else: ?>
|
<?php elseif ($query !== ''): ?>
|
||||||
<p> Es sind noch keine Beiträge in dieser Kategorie enthalten.</p>
|
<p>Keine Beiträge in dieser Kategorie zu diesem Suchbegriff gefunden.</p>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<p>Keine Beiträge in dieser Kategorie gefunden.</p>
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php unset($_SESSION["message"]); ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
<div class="s-res-pagination-footer">
|
||||||
|
|
||||||
|
<!-- 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" form="cat-filter-form" 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>
|
||||||
|
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>100</option>
|
||||||
|
</select>
|
||||||
|
<noscript><button type="submit" form="cat-filter-form" class="nav__search-button">Übernehmen</button></noscript>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="s-res-page-navigation" aria-label="Seitennavigation">
|
||||||
|
<!-- No-JS-Fallback: -->
|
||||||
|
<noscript>
|
||||||
|
<?php echo renderNoJsPaginationCategory($currentPage, $totalPages, $category, $currentSort, $query, $limit); ?>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- JS-Version: wird per js/showCategory.js befüllt/eingeblendet: -->
|
||||||
|
<div id="js-page-navigation" style="display:none;">
|
||||||
|
<button type="button" class="s-res-page-btn" id="prev-page-btn">«</button>
|
||||||
|
<span id="dynamic-page-numbers"></span>
|
||||||
|
<button type="button" class="s-res-page-btn" id="next-page-btn">»</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
+222
-82
@@ -10,62 +10,11 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
Seite: Beitrag erstellen
|
Seite: Beitrag erstellen
|
||||||
Inhalt: Formular für die Erstellung eines neuen Beitrags
|
Inhalt: Formular für die Erstellung eines neuen Beitrags
|
||||||
-->
|
-->
|
||||||
<form method="post" action="php/controller/updateArticle-controller.php?id=<?php if(isset($id) && !empty($id)){echo htmlspecialchars($id);}else{$_SESSION["message"] = "missing_id";} ?>" id="editor-form" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
<form method="post" action="php/controller/updateArticle-controller.php?id=<?php if(isset($id) && !empty($id)){echo htmlspecialchars($id);}else{$_SESSION["message"] = "missing_id";} ?>" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
||||||
|
|
||||||
<main class="editor-main">
|
<main class="editor-main">
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "internal_error"): ?>
|
<?php include_once "includes/alertMessages.php"?>
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein interner Fehler beim Speichern aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_id"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Es ist ein Fehler aufgetreten. Die ID konnte nicht ausgelesen werden. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "missing_parameters"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Jeder Beitrag muss einen Titel, Kategorie und Inhalt besitzen.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_title"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Der Titel enthält ungültige Zeichen oder erfüllt die Länge von 5-120 Zeichen nicht.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_content"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Der Text erlaubt eine Länge von 10 bis maximal 7.000 Zeichen (ca. 1.000 Wörter).
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Die ausgewählte Kategorie ist ungültig.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_tags"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Ungültige Schlagworte gefunden. Erlaubt sind nur Buchstaben, Zahlen, Leerzeichen und Bindestriche (2-20 Zeichen).
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "validation_missing"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Bei der Validierung deiner Daten ist ein Fehler aufgetreten. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "unauthorized_access"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Sie sind nicht berechtigt, diesen Beitrag zu bearbeiten.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "not_found_article"): ?>
|
|
||||||
<p class="alert-message is-error">
|
|
||||||
Der zu bearbeitende Artikel wurde nicht gefunden. Bitte versuche es erneut.
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php
|
|
||||||
unset($_SESSION["message"]);
|
|
||||||
?>
|
|
||||||
<input type="text" id="title" name="title"
|
<input type="text" id="title" name="title"
|
||||||
value="<?php
|
value="<?php
|
||||||
if (isset($_SESSION['old_title']) && !empty($_SESSION['old_title'])){
|
if (isset($_SESSION['old_title']) && !empty($_SESSION['old_title'])){
|
||||||
@@ -77,8 +26,33 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
|
|
||||||
?>"
|
?>"
|
||||||
placeholder="Titel hier eingeben" required>
|
placeholder="Titel hier eingeben" required>
|
||||||
<textarea id="content" name="content" placeholder="Schreibe deinen Beitrag..."><?php if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){echo htmlspecialchars($_SESSION['old_content']); unset($_SESSION['old_content']);}elseif (isset($content) && !empty($content)){echo htmlspecialchars($content);}?></textarea>
|
|
||||||
|
|
||||||
|
<!-- Hier werden die dynamischen divs via JavaScript eingefügt -->
|
||||||
|
<div id="block-container"></div>
|
||||||
|
|
||||||
|
<!-- Plus-Button und das Pop-up-Menü -->
|
||||||
|
<div id="add-block-control" class="article-editor-scope add-block-control">
|
||||||
|
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
||||||
|
<div id="block-popup" class="article-editor-scope block-popup hidden">
|
||||||
|
<button type="button" data-type="text">Textblock</button>
|
||||||
|
<button type="button" data-type="image">Bild einfügen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unsichtbares Textfeld, das die JSON-Daten hält und an den Controller postet -->
|
||||||
|
<textarea id="content" name="content" style="display:none;"><?php
|
||||||
|
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){
|
||||||
|
echo htmlspecialchars($_SESSION['old_content']);
|
||||||
|
unset($_SESSION['old_content']);
|
||||||
|
}elseif (isset($content) && !empty($content)){
|
||||||
|
echo htmlspecialchars($content);
|
||||||
|
} else {
|
||||||
|
echo '[]';
|
||||||
|
}
|
||||||
|
?></textarea>
|
||||||
|
|
||||||
|
<!-- unsichtbares Input, um die zu löschenden Bilder zu übergeben-->
|
||||||
|
<input type="hidden" id="deleted-images" name="deleted_images" value="[]">
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Seitenleiste -->
|
<!-- Seitenleiste -->
|
||||||
@@ -91,45 +65,211 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
<div class="sidebar-block">
|
<div class="sidebar-block">
|
||||||
<label for="category">Kategorie <span class="required">*</span></label>
|
<label for="category">Kategorie <span class="required">*</span></label>
|
||||||
<select id="category" name="category" required>
|
<select id="category" name="category" required>
|
||||||
<option disabled selected>Kategorie wählen...</option>
|
<option disabled <?php
|
||||||
|
if ((!isset($_SESSION['old_category']) || empty($_SESSION['old_category'])) && (!isset($category) || empty($category))) {
|
||||||
|
echo 'selected';
|
||||||
|
}
|
||||||
|
?>>Kategorie wählen...</option>
|
||||||
|
|
||||||
<optgroup label="Sprachen">
|
<optgroup label="Sprachen">
|
||||||
<option value="deutsch">Deutsch</option>
|
<option value="deutsch" <?php
|
||||||
<option value="englisch">Englisch</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="franzoesisch">Französisch</option>
|
if ($_SESSION['old_category'] === 'deutsch') { echo 'selected'; }
|
||||||
<option value="latein">Latein</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="literatur">Literatur</option>
|
if ($category === 'deutsch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Deutsch</option>
|
||||||
|
<option value="englisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'englisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'englisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Englisch</option>
|
||||||
|
<option value="franzoesisch" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'franzoesisch') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'franzoesisch') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Französisch</option>
|
||||||
|
<option value="latein" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'latein') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'latein') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Latein</option>
|
||||||
|
<option value="literatur" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'literatur') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'literatur') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Literatur</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="MINT">
|
<optgroup label="MINT">
|
||||||
<option value="mathe">Mathematik</option>
|
<option value="mathe" <?php
|
||||||
<option value="biologie">Biologie</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="chemie">Chemie</option>
|
if ($_SESSION['old_category'] === 'mathe') { echo 'selected'; }
|
||||||
<option value="physik">Physik</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="informatik">Informatik</option>
|
if ($category === 'mathe') { echo 'selected'; }
|
||||||
<option value="astronomie">Astronomie</option>
|
}
|
||||||
|
?>>Mathematik</option>
|
||||||
|
<option value="biologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'biologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'biologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Biologie</option>
|
||||||
|
<option value="chemie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'chemie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'chemie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Chemie</option>
|
||||||
|
<option value="physik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'physik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'physik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Physik</option>
|
||||||
|
<option value="informatik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'informatik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'informatik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Informatik</option>
|
||||||
|
<option value="astronomie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'astronomie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'astronomie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Astronomie</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="Gesellschaft & Werte">
|
<optgroup label="Gesellschaft & Werte">
|
||||||
<option value="geschichte">Geschichte</option>
|
<option value="geschichte" <?php
|
||||||
<option value="erdkunde">Erdkunde</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="sozialkunde">Sozialkunde</option>
|
if ($_SESSION['old_category'] === 'geschichte') { echo 'selected'; }
|
||||||
<option value="wirtschaft">Wirtschaftskunde</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
<option value="religion">Religion</option>
|
if ($category === 'geschichte') { echo 'selected'; }
|
||||||
<option value="ethik">Ethikunterricht</option>
|
}
|
||||||
<option value="philosophie">Philosophie</option>
|
?>>Geschichte</option>
|
||||||
<option value="psychologie">Psychologie</option>
|
<option value="erdkunde" <?php
|
||||||
<option value="kunst">Kunst</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="musik">Musik</option>
|
if ($_SESSION['old_category'] === 'erdkunde') { echo 'selected'; }
|
||||||
<option value="theater">Theater</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'erdkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Erdkunde</option>
|
||||||
|
<option value="sozialkunde" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sozialkunde') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sozialkunde') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sozialkunde</option>
|
||||||
|
<option value="wirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'wirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'wirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Wirtschaftskunde</option>
|
||||||
|
<option value="religion" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'religion') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'religion') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Religion</option>
|
||||||
|
<option value="ethik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'ethik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'ethik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Ethikunterricht</option>
|
||||||
|
<option value="philosophie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'philosophie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'philosophie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Philosophie</option>
|
||||||
|
<option value="psychologie" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'psychologie') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'psychologie') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Psychologie</option>
|
||||||
|
<option value="kunst" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'kunst') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'kunst') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Kunst</option>
|
||||||
|
<option value="musik" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'musik') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'musik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Musik</option>
|
||||||
|
<option value="theater" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'theater') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'theater') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Theater</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
<optgroup label="Technik & Praxis">
|
<optgroup label="Technik & Praxis">
|
||||||
<option value="technik">Technik</option>
|
<option value="technik" <?php
|
||||||
<option value="werken">Werken</option>
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
<option value="hauswirtschaft">Hauswirtschaft</option>
|
if ($_SESSION['old_category'] === 'technik') { echo 'selected'; }
|
||||||
<option value="sport">Sport</option>
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'technik') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Technik</option>
|
||||||
|
<option value="werken" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'werken') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'werken') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Werken</option>
|
||||||
|
<option value="hauswirtschaft" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'hauswirtschaft') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Hauswirtschaft</option>
|
||||||
|
<option value="sport" <?php
|
||||||
|
if (isset($_SESSION['old_category']) && !empty($_SESSION['old_category'])) {
|
||||||
|
if ($_SESSION['old_category'] === 'sport') { echo 'selected'; }
|
||||||
|
} elseif (isset($category) && !empty($category)) {
|
||||||
|
if ($category === 'sport') { echo 'selected'; }
|
||||||
|
}
|
||||||
|
?>>Sport</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['old_category'])) {
|
||||||
|
unset($_SESSION['old_category']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,135 @@
|
|||||||
padding: 18px 12px;
|
padding: 18px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Container für die dynamisch per JS eingefügten Blöcke */
|
||||||
|
.article-editor-scope #block-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling für jeden einzelnen dynamisch generierten Block */
|
||||||
|
.article-editor-scope .editor-block {
|
||||||
|
position: relative; /* Wichtig für die absolute Positionierung des Lösch-Buttons */
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px dashed #cccccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea innerhalb eines dynamischen Textblocks */
|
||||||
|
.article-editor-scope .editor-block textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Komfort-Löschbutton oben rechts an jedem Block */
|
||||||
|
.article-editor-scope .delete-block-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: -10px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e74c3c;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .delete-block-btn:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Steuerungselement für den Plus-Button und das Pop-up */
|
||||||
|
.article-editor-scope .add-block-control {
|
||||||
|
position: relative; /* Dient als Anker für das absolut positionierte Pop-up */
|
||||||
|
margin-top: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
align-self: flex-start; /* Verhindert, dass der Button die volle Breite spannt */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Der runde Plus-Button */
|
||||||
|
.article-editor-scope .plus-button {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .plus-button:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Das Pop-up Menü */
|
||||||
|
.article-editor-scope .block-popup {
|
||||||
|
position: absolute;
|
||||||
|
left: 60px; /* Platziert das Menü rechts neben dem Plus-Button */
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%); /* Zentriert das Menü vertikal zum Button */
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Die entscheidende Klasse zum Ausblenden */
|
||||||
|
.article-editor-scope .block-popup.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons im Pop-up (Textblock / Bild einfügen) */
|
||||||
|
.article-editor-scope .block-popup button {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dcdde1;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #2f3640;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .block-popup button:hover {
|
||||||
|
background-color: #f1f2f6;
|
||||||
|
border-color: #b2bec3;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.article-editor-scope.editor-container {
|
.article-editor-scope.editor-container {
|
||||||
@@ -113,4 +242,8 @@
|
|||||||
border-left: none;
|
border-left: none;
|
||||||
border-top: 1px solid #e0e0e0;
|
border-top: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-editor-scope .editor-block textarea {
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+15
-15
@@ -130,13 +130,6 @@ h1 {
|
|||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-label {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
width: 100%;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-input {
|
.login-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@@ -210,14 +203,6 @@ h1 {
|
|||||||
.form-container {
|
.form-container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
padding: 30px;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #dbe3ec;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-container {
|
|
||||||
flex: 1 1 450px;
|
flex: 1 1 450px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@@ -226,3 +211,18 @@ h1 {
|
|||||||
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
|
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Darstellung der Registrierungsbestätigung */
|
||||||
|
.confirm-message {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anpassung des Login-Buttons auf der Bestätigungsseite */
|
||||||
|
.confirm-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
+39
-22
@@ -57,6 +57,10 @@ CSS für die navbar
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav__dropdown-menu a:hover {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
.nav__link {
|
.nav__link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -89,6 +93,12 @@ CSS für die navbar
|
|||||||
border-radius: 4px; /* Abgerundete Ecken */
|
border-radius: 4px; /* Abgerundete Ecken */
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search input {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav__search-button {
|
.nav__search-button {
|
||||||
@@ -115,8 +125,33 @@ CSS für die navbar
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive Anpassung unter 1210px */
|
.nav__search-form {
|
||||||
@media (max-width: 1210px) {
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__search-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Anpassung unter 1240px */
|
||||||
|
@media (max-width: 1240px) {
|
||||||
.nav {
|
.nav {
|
||||||
flex-wrap: wrap; /* zweite Nav-Zeile*/
|
flex-wrap: wrap; /* zweite Nav-Zeile*/
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
@@ -151,8 +186,8 @@ CSS für die navbar
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
/* Responsive Anpassungen unter 900px (für z.B. Smartphones) */
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 900px) {
|
||||||
.nav {
|
.nav {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -189,24 +224,6 @@ CSS für die navbar
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav__search {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav__search-button {
|
|
||||||
display: inline-block;
|
|
||||||
background: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav__search-button:hover {
|
.nav__search-button:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,17 @@ CSS für die Suchergebnis-Seite
|
|||||||
cursor: not-allowed;
|
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) */
|
/* Responsive Anpassungen unter 760px (für z.B. Smartphones) */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.s-res-layout-grid {
|
.s-res-layout-grid {
|
||||||
|
|||||||
@@ -105,6 +105,37 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-view-content {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem; /* Erzeugt einen sauberen Abstand zwischen den einzelnen Blöcken */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: #2d3748;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-text {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-image {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Zentriert das Bild horizontal */
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-view-body.block-image img {
|
||||||
|
max-width: 100%; /* Verhindert das Ausbrechen aus der Lesebreite */
|
||||||
|
height: auto; /* Behält das originale Seitenverhältnis bei */
|
||||||
|
border-radius: 6px; /* Optionale leichte Rundung für ein moderneres Layout */
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03); /* Minimaler, eleganter Schatten */
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Anpassungen unter 760px (für z.B. Smarticlephones) */
|
/* Responsive Anpassungen unter 760px (für z.B. Smarticlephones) */
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.article-view-container {
|
.article-view-container {
|
||||||
@@ -116,3 +147,140 @@
|
|||||||
font-size: 1.85rem;
|
font-size: 1.85rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- KOMMENTARE --- */
|
||||||
|
|
||||||
|
.article-comments-section {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-comments-section h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comments-list {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-content {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 130px;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment-form .button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-button {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-button:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-left: 2rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 3px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-reply {
|
||||||
|
background-color: #eef6ff;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-info {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.comment-login-hint {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/* --- ZWEISPALTIIGES GRID-LAYOUT --- */
|
||||||
|
.cat-view-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 95%; /* 95% Monitorbreite wie das Original */
|
||||||
|
width: 95%;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color: #212529;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
/* Grid für die Zweispaltigkeit */
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px 1fr; /* 320px Sidebar, Rest für Content */
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-container * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- SEITENLEISTE (SORTIERUNG) --- */
|
||||||
|
.cat-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-sidebar-box {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-sidebar-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-filter-option input[type="radio"] {
|
||||||
|
margin: 0;
|
||||||
|
accent-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- HAUPTINHALT --- */
|
||||||
|
.cat-view-main {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #1a202c;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-meta-text {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BEITRÄGE LISTE --- */
|
||||||
|
.cat-view-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-link {
|
||||||
|
color: #3182ce;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #2b6cb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-meta-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-author {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #4a5568;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-author-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-likes {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-arrow {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #a0aec0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
transition: color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-item:hover .cat-view-arrow {
|
||||||
|
color: #3182ce;
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- FOOTER PAGINATION --- */
|
||||||
|
.cat-view-pagination-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-navigation {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
color: #4a5568;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn:hover:not(:disabled) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .cat-active-btn {
|
||||||
|
background-color: #3182ce;
|
||||||
|
border-color: #3182ce;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-view-pagination-footer .s-res-page-btn:disabled:not(.cat-active-btn) {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
color: #94a3b8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESPONSIVE ANPASSUNG (Unter 768px) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cat-view-container {
|
||||||
|
grid-template-columns: 1fr; /* Stapelt Sidebar und Inhalt untereinander */
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
.cat-view-pagination-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_content"): ?>
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_content"): ?>
|
||||||
<p class="alert-message is-error">
|
<p class="alert-message is-error">
|
||||||
Der Text erlaubt eine Länge von 10 bis maximal 7.000 Zeichen (ca. 1.000 Wörter).
|
Ein Beitrag muss Inhalt besitzen. Text- und Bildelemente dürfen nicht leer sein!
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_category"): ?>
|
||||||
@@ -58,6 +58,42 @@
|
|||||||
Der Beitrag wurde erfolgreich gelöscht.
|
Der Beitrag wurde erfolgreich gelöscht.
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "new_article"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "registration_confirmed"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Ihre Registrierung wurde erfolgreich abgeschlossen. Sie können sich jetzt anmelden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "password_changed"): ?>
|
||||||
|
<p class="alert-message is-success">
|
||||||
|
Ihr Passwort wurde erfolgreich geändert. Sie können sich jetzt anmelden.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "database_error"): ?>
|
||||||
|
<p class="alert-message is-error">
|
||||||
|
Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
<?php
|
<?php
|
||||||
unset($_SESSION["message"]);
|
unset($_SESSION["message"]);
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<a href="index.php?pfad=impressum">Impressum</a>
|
<a href="index.php?pfad=impressum">Impressum</a>
|
||||||
<a href="index.php?pfad=datenschutz">Datenschutz</a>
|
<a href="index.php?pfad=datenschutz">Datenschutz</a>
|
||||||
<a href="index.php?pfad=nutzungsbedingungen">Nutzungsbedingungen</a>
|
<a href="index.php?pfad=nutzungsbedingungen">Nutzungsbedingungen</a>
|
||||||
|
<a href="index.php?pfad=accessibility">Barrierefreiheit</a>
|
||||||
</footer>
|
</footer>
|
||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Hilfsfunktionen rund um die Suchergebnisse.
|
||||||
|
*
|
||||||
|
* Werden sowohl von content/search-results.php (No-JS-Rendering)
|
||||||
|
* als auch von php/controller/search-results-data.php (JSON für
|
||||||
|
* die JS-Hydration) genutzt, damit Sortier-/Filterlogik nur an
|
||||||
|
* einer Stelle gepflegt werden muss
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getLikeCount($item): int
|
||||||
|
{
|
||||||
|
if (isset($item['likes']) && is_array($item['likes'])) {
|
||||||
|
return count($item['likes']);
|
||||||
|
}
|
||||||
|
return (int)($item['likes'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sortiert die Ergebnisse
|
||||||
|
*/
|
||||||
|
function sortSearchResults(array $results, string $sortStyle): array
|
||||||
|
{
|
||||||
|
switch ($sortStyle) {
|
||||||
|
case 'likes':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return getLikeCount($b) <=> getLikeCount($a);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'newest':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcmp($b['creationDate'] ?? '', $a['creationDate'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'oldest':
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcmp($a['creationDate'] ?? '', $b['creationDate'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'alphabet':
|
||||||
|
default:
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcasecmp($a['title'] ?? '', $b['title'] ?? '');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtert die Ergebnisse ('all' = kein Filter).
|
||||||
|
*/
|
||||||
|
function filterSearchResultsByCategory(array $results, string $category): array
|
||||||
|
{
|
||||||
|
$category = strtolower(trim($category));
|
||||||
|
if ($category === '' || $category === 'all') {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
return array_values(array_filter($results, function ($item) use ($category) {
|
||||||
|
return strtolower($item['category'] ?? '') === $category;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Such-Ergebnis-URL für einen bestimmten Seitenwechsel (No-JS-Pagination).
|
||||||
|
*/
|
||||||
|
function buildSearchResultsUrl(int $page, string $query, string $sort, string $category, int $limit): string
|
||||||
|
{
|
||||||
|
return "index.php?pfad=search-results"
|
||||||
|
. "&q=" . urlencode($query)
|
||||||
|
. "&sort=" . urlencode($sort)
|
||||||
|
. "&category=" . urlencode($category)
|
||||||
|
. "&limit=" . $limit
|
||||||
|
. "&page=" . $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert eine rein serverseitige Pagination für den No-JS-Fall:
|
||||||
|
*/
|
||||||
|
function renderNoJsPagination(int $currentPage, int $totalPages, string $query, string $sort, string $category, int $limit): string
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if ($currentPage > 1) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($currentPage - 1, $query, $sort, $category, $limit)) . '">«</a> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">«</span> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $totalPages; $i++) {
|
||||||
|
if ($i === $currentPage) {
|
||||||
|
$html .= '<span class="s-res-page-btn s-res-page-btn-active">' . $i . '</span> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($i, $query, $sort, $category, $limit)) . '">' . $i . '</a> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentPage < $totalPages) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildSearchResultsUrl($currentPage + 1, $query, $sort, $category, $limit)) . '">»</a>';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">»</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtert Ergebnisse anhand eines Suchbegriffs
|
||||||
|
*
|
||||||
|
* Wird von content/showCategory.php genutzt, um innerhalb einer bereits
|
||||||
|
* geladenen Kategorie zu suchen.
|
||||||
|
*/
|
||||||
|
function filterResultsByQuery(array $results, string $query): array
|
||||||
|
{
|
||||||
|
$query = trim($query);
|
||||||
|
if ($query === '') {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
$needle = mb_strtolower($query);
|
||||||
|
return array_values(array_filter($results, function ($item) use ($needle) {
|
||||||
|
$haystack = mb_strtolower(($item['title'] ?? '') . ' ' . ($item['content'] ?? ''));
|
||||||
|
return mb_strpos($haystack, $needle) !== false;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Kategorie-URL für einen bestimmten Seitenwechsel (No-JS-Pagination).
|
||||||
|
* Analog zu buildSearchResultsUrl, aber für pfad=showCategory (feste Kategorie,
|
||||||
|
* dafür mit Freitext-Suche innerhalb der Kategorie statt Kategorie-Filter).
|
||||||
|
*/
|
||||||
|
function buildCategoryUrl(int $page, string $category, string $sort, string $query, int $limit): string
|
||||||
|
{
|
||||||
|
return "index.php?pfad=showCategory"
|
||||||
|
. "&category=" . urlencode($category)
|
||||||
|
. "&sort=" . urlencode($sort)
|
||||||
|
. "&q=" . urlencode($query)
|
||||||
|
. "&limit=" . $limit
|
||||||
|
. "&page=" . $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert eine rein serverseitige Pagination für showCategory (No-JS-Fall).
|
||||||
|
* Analog zu renderNoJsPagination.
|
||||||
|
*/
|
||||||
|
function renderNoJsPaginationCategory(int $currentPage, int $totalPages, string $category, string $sort, string $query, int $limit): string
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if ($currentPage > 1) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($currentPage - 1, $category, $sort, $query, $limit)) . '">«</a> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">«</span> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $totalPages; $i++) {
|
||||||
|
if ($i === $currentPage) {
|
||||||
|
$html .= '<span class="s-res-page-btn s-res-page-btn-active">' . $i . '</span> ';
|
||||||
|
} else {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($i, $category, $sort, $query, $limit)) . '">' . $i . '</a> ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentPage < $totalPages) {
|
||||||
|
$html .= '<a class="s-res-page-btn" href="' . htmlspecialchars(buildCategoryUrl($currentPage + 1, $category, $sort, $query, $limit)) . '">»</a>';
|
||||||
|
} else {
|
||||||
|
$html .= '<span class="s-res-page-btn" aria-disabled="true">»</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
Suchleiste. Wird via PHP später in alle Seiten eingebunden
|
Suchleiste. Wird via PHP später in alle Seiten eingebunden
|
||||||
-->
|
-->
|
||||||
<!--<label for="site-search">Suche</label>-->
|
<!--<label for="site-search">Suche</label>-->
|
||||||
<form action="php/controller/search-results-controller.php" method="GET" class="search-form" style="display: flex; align-items: center; gap: 5px;">
|
<form action="php/controller/search-results-controller.php" method="GET" class="nav__search-form">
|
||||||
|
|
||||||
<input type="hidden" name="pfad" value="search-results">
|
<input type="hidden" name="pfad" value="search-results">
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
ob_start();
|
ob_start();
|
||||||
|
include_once("php/controller/index.php");
|
||||||
|
|
||||||
$pfad = $_GET["pfad"] ?? "home";
|
$pfad = $_GET["pfad"] ?? "home";
|
||||||
|
|
||||||
@@ -12,9 +15,24 @@ if ($pfad === "login") {
|
|||||||
include_once "php/controller/login-controller.php";
|
include_once "php/controller/login-controller.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($pfad === "search-results-controller") {
|
||||||
|
include_once "php/controller/search-results-controller.php";
|
||||||
|
}
|
||||||
|
|
||||||
if ($pfad === "register") {
|
if ($pfad === "register") {
|
||||||
include_once "php/controller/register-controller.php";
|
include_once "php/controller/register-controller.php";
|
||||||
}
|
}
|
||||||
|
if ($pfad === "password-forgotten") {
|
||||||
|
include_once "php/controller/password-forgotten-controller.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pfad === "confirm-register") {
|
||||||
|
include_once "php/controller/confirm-register-controller.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pfad === "confirm-password") {
|
||||||
|
include_once "php/controller/confirm-password-controller.php";
|
||||||
|
}
|
||||||
|
|
||||||
if ($pfad === "logout") {
|
if ($pfad === "logout") {
|
||||||
include_once "php/controller/logout-controller.php";
|
include_once "php/controller/logout-controller.php";
|
||||||
@@ -40,6 +58,7 @@ if ($pfad === "deleteAccount") {
|
|||||||
<meta name="author" content="Niklas Ortmann">
|
<meta name="author" content="Niklas Ortmann">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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="icon" type="image/x-icon" href="images/logos/logo_icon.ico">
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
<link rel="stylesheet" href="css/navbar.css">
|
<link rel="stylesheet" href="css/navbar.css">
|
||||||
<link rel="stylesheet" href="css/footer.css">
|
<link rel="stylesheet" href="css/footer.css">
|
||||||
@@ -48,6 +67,14 @@ if ($pfad === "deleteAccount") {
|
|||||||
<link rel="stylesheet" href="css/profile.css">
|
<link rel="stylesheet" href="css/profile.css">
|
||||||
<link rel="stylesheet" href="css/showArticle.css">
|
<link rel="stylesheet" href="css/showArticle.css">
|
||||||
<link rel="stylesheet" href="css/message.css">
|
<link rel="stylesheet" href="css/message.css">
|
||||||
|
<link rel="stylesheet" href="css/showCategory.css">
|
||||||
|
|
||||||
|
<script src="js/comments.js" defer></script>
|
||||||
|
<script src="js/editor.js" async></script>
|
||||||
|
<script src="js/search-results.js" async></script>
|
||||||
|
<script src="js/showCategory.js" async></script>
|
||||||
|
|
||||||
|
|
||||||
<title>EduForge</title>
|
<title>EduForge</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Initialisiert die Kommentarfunktion.
|
||||||
|
*
|
||||||
|
* Kommentare werden per AJAX gespeichert,
|
||||||
|
* ohne dass die Seite neu geladen werden muss.
|
||||||
|
*/
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const form = document.getElementById("comment-form");
|
||||||
|
const commentsList = document.getElementById("comments-list");
|
||||||
|
const commentContent = document.getElementById("comment-content");
|
||||||
|
const parentCommentInput = document.getElementById("parent-comment-id");
|
||||||
|
const replyInfo = document.getElementById("reply-info");
|
||||||
|
|
||||||
|
if (!form || !commentsList || !commentContent || !parentCommentInput || !replyInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktiviert einen einzelnen Antworten-Button.
|
||||||
|
*
|
||||||
|
* @param {HTMLButtonElement} button Antworten-Button
|
||||||
|
*/
|
||||||
|
function registerReplyButton(button) {
|
||||||
|
button.addEventListener("click", function () {
|
||||||
|
parentCommentInput.value = button.dataset.commentId;
|
||||||
|
replyInfo.textContent = "Antwort auf " + button.dataset.author;
|
||||||
|
replyInfo.style.display = "block";
|
||||||
|
commentContent.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registriert alle bereits vorhandenen Antwort-Buttons.
|
||||||
|
*/
|
||||||
|
document.querySelectorAll(".reply-button").forEach(function (button) {
|
||||||
|
registerReplyButton(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendet Kommentare per AJAX an den Server.
|
||||||
|
*/
|
||||||
|
form.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const parentCommentId = parentCommentInput.value;
|
||||||
|
|
||||||
|
fetch("php/ajax/add-comment.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
alert(data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyMessage = commentsList.querySelector(".no-comments-message");
|
||||||
|
|
||||||
|
if (emptyMessage) {
|
||||||
|
emptyMessage.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentElement = document.createElement("div");
|
||||||
|
commentElement.classList.add("comment-item");
|
||||||
|
|
||||||
|
if (parentCommentId) {
|
||||||
|
commentElement.classList.add("comment-reply");
|
||||||
|
|
||||||
|
commentElement.innerHTML = `
|
||||||
|
<p>
|
||||||
|
<strong>${escapeHtml(data.author)}</strong>
|
||||||
|
<span>${escapeHtml(data.created)}</span>
|
||||||
|
</p>
|
||||||
|
<p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const parentReplies = document.querySelector(
|
||||||
|
`.comment-item[data-comment-id="${parentCommentId}"] .comment-replies`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parentReplies) {
|
||||||
|
parentReplies.appendChild(commentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
commentElement.dataset.commentId = data.commentId;
|
||||||
|
|
||||||
|
commentElement.innerHTML = `
|
||||||
|
<p>
|
||||||
|
<strong>${escapeHtml(data.author)}</strong>
|
||||||
|
<span>${escapeHtml(data.created)}</span>
|
||||||
|
</p>
|
||||||
|
<p>${escapeHtml(data.content).replace(/\n/g, "<br>")}</p>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="reply-button"
|
||||||
|
data-comment-id="${escapeHtml(data.commentId)}"
|
||||||
|
data-author="${escapeHtml(data.author)}">
|
||||||
|
Antworten
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="comment-replies"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
commentsList.prepend(commentElement);
|
||||||
|
|
||||||
|
const newReplyButton = commentElement.querySelector(".reply-button");
|
||||||
|
|
||||||
|
if (newReplyButton) {
|
||||||
|
registerReplyButton(newReplyButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commentContent.value = "";
|
||||||
|
parentCommentInput.value = "";
|
||||||
|
replyInfo.textContent = "";
|
||||||
|
replyInfo.style.display = "none";
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert("Kommentar konnte nicht gesendet werden.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt HTML-Sonderzeichen aus Nutzereingaben.
|
||||||
|
*
|
||||||
|
* @param {string} text Zu bereinigender Text
|
||||||
|
* @returns {string} Sicherer Text
|
||||||
|
*/
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
console.log("Die JavaScript-Datei wurde erfolgreich geladen!");
|
||||||
|
|
||||||
|
function initEditor() {
|
||||||
|
const form = document.getElementById("editor-form");
|
||||||
|
|
||||||
|
if (!form) {
|
||||||
|
console.error("Skript abgebrochen: Formular nicht gefunden!");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log("Formular gefunden und Editor initialisiert:", form);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById("block-container");
|
||||||
|
const plusButton = document.getElementById("plus-button");
|
||||||
|
const popup = document.getElementById("block-popup");
|
||||||
|
const hiddenContentInput = document.getElementById("content");
|
||||||
|
|
||||||
|
const initialImages = [];
|
||||||
|
|
||||||
|
// Pop-up umschalten bei Klick auf das Plus
|
||||||
|
plusButton.addEventListener("click", () => {
|
||||||
|
popup.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Klick auf eine Block-Option im Pop-up
|
||||||
|
popup.querySelectorAll("button").forEach(btn => {
|
||||||
|
btn.addEventListener("click", function() {
|
||||||
|
const type = this.getAttribute("data-type");
|
||||||
|
addBlockElement(type, "");
|
||||||
|
popup.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Erstellt ein visuelles HTML-Element im Editor
|
||||||
|
function addBlockElement(type, value = "") {
|
||||||
|
const blockDiv = document.createElement("div");
|
||||||
|
blockDiv.classList.add("editor-block");
|
||||||
|
blockDiv.setAttribute("data-type", type);
|
||||||
|
|
||||||
|
// Wenn es ein existierendes Server-Bild beim Laden ist, Pfad im globalen Array sichern
|
||||||
|
if (type === "image" && value && typeof value === 'string' && value.startsWith('uploads/')) {
|
||||||
|
initialImages.push(value);
|
||||||
|
blockDiv.setAttribute("data-value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Löschen-Button
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.type = "button";
|
||||||
|
deleteBtn.innerHTML = "✕";
|
||||||
|
deleteBtn.classList.add("delete-block-btn");
|
||||||
|
deleteBtn.addEventListener("click", () => {
|
||||||
|
// ANPASSUNG 2B: Logik hier komplett geleert. Das '✕' entfernt den Block jetzt nur noch sicher aus dem HTML.
|
||||||
|
blockDiv.remove();
|
||||||
|
});
|
||||||
|
blockDiv.appendChild(deleteBtn);
|
||||||
|
|
||||||
|
if (type === "text") {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.placeholder = "Schreibe deinen Textblock...";
|
||||||
|
textarea.value = value;
|
||||||
|
blockDiv.appendChild(textarea);
|
||||||
|
} else if (type === "image") {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.accept = "image/*";
|
||||||
|
|
||||||
|
const imgPreview = document.createElement("img");
|
||||||
|
imgPreview.style.maxWidth = "200px";
|
||||||
|
imgPreview.style.display = "block";
|
||||||
|
imgPreview.style.marginTop = "10px";
|
||||||
|
|
||||||
|
if (value && typeof value === 'string') {
|
||||||
|
if (value.startsWith('uploads/') || value.startsWith('data:image/')) {
|
||||||
|
imgPreview.src = value;
|
||||||
|
blockDiv.setAttribute("data-value", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", function() {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
imgPreview.src = e.target.result;
|
||||||
|
blockDiv.setAttribute("data-value", e.target.result);
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
blockDiv.appendChild(fileInput);
|
||||||
|
blockDiv.appendChild(imgPreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(blockDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// beim Abschicken verbleibende Blöcke auslesen UND gelöschte Bilder ermitteln
|
||||||
|
form.addEventListener("submit", function(e) {
|
||||||
|
const blocks = [];
|
||||||
|
const currentImages = [];
|
||||||
|
|
||||||
|
// alle aktuell im Formular verbliebenen Blöcke scannen
|
||||||
|
container.querySelectorAll(".editor-block").forEach(blockDiv => {
|
||||||
|
const type = blockDiv.getAttribute("data-type");
|
||||||
|
let value = "";
|
||||||
|
|
||||||
|
if (type === "text") {
|
||||||
|
value = blockDiv.querySelector("textarea").value;
|
||||||
|
} else if (type === "image") {
|
||||||
|
|
||||||
|
const imgTag = blockDiv.querySelector("img");
|
||||||
|
if (imgTag) {
|
||||||
|
const srcValue = imgTag.getAttribute("src") || "";
|
||||||
|
// Wenn es ein neues Bild ist, nutzen wir das data-value (Base64)
|
||||||
|
if (srcValue.startsWith('data:image/')) {
|
||||||
|
value = blockDiv.getAttribute("data-value") || "";
|
||||||
|
} else {
|
||||||
|
value = srcValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfade sammeln, die der Nutzer NICHT gelöscht hat (für den Abgleich)
|
||||||
|
if (value && value.startsWith('uploads/')) {
|
||||||
|
currentImages.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push({ type: type, value: value });
|
||||||
|
});
|
||||||
|
|
||||||
|
// das reguläre unsichtbare Content-Feld befüllen
|
||||||
|
hiddenContentInput.value = JSON.stringify(blocks);
|
||||||
|
|
||||||
|
// Differenz berechnen: Welche Bilder aus 'initialImages' fehlen in 'currentImages' ?
|
||||||
|
const deletedImages = initialImages.filter(img => !currentImages.includes(img));
|
||||||
|
|
||||||
|
// das 'deleted_images'-Feld dynamisch befüllen und an den Controller senden
|
||||||
|
let deletedInput = document.getElementById("deleted-images");
|
||||||
|
if (!deletedInput) {
|
||||||
|
deletedInput = document.createElement("input");
|
||||||
|
deletedInput.type = "hidden";
|
||||||
|
deletedInput.id = "deleted-images";
|
||||||
|
deletedInput.name = "deleted_images";
|
||||||
|
form.appendChild(deletedInput);
|
||||||
|
}
|
||||||
|
deletedInput.value = JSON.stringify(deletedImages);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Existierende Blöcke laden (stellt alte Daten aus der Session wieder her)
|
||||||
|
try {
|
||||||
|
const initialBlocks = JSON.parse(hiddenContentInput.value.trim());
|
||||||
|
if (Array.isArray(initialBlocks)) {
|
||||||
|
initialBlocks.forEach(b => {
|
||||||
|
if (b.type === "image" && b.value && typeof b.value === 'string' && !b.value.startsWith('data:image/')) {
|
||||||
|
let cleanPath = b.value.trim().replace(/\\\//g, '/'); // Verwandelt \/ in /
|
||||||
|
|
||||||
|
initialImages.push(cleanPath);
|
||||||
|
addBlockElement(b.type, cleanPath);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
addBlockElement(b.type, b.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("Erfolgreich registrierte Start-Bilder:", initialImages);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
if (hiddenContentInput.value.trim() !== "") {
|
||||||
|
addBlockElement("text", hiddenContentInput.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SICHERER START: Prüft, ob das HTML bereits bereit ist
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", initEditor);
|
||||||
|
} else {
|
||||||
|
// Falls das DOM schon fertig geladen ist, führen wir es direkt aus
|
||||||
|
initEditor();
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Übernimmt Sortierung, Kategorie-Filter und Pagination der Suchergebnisse
|
||||||
|
* vollständig clientseitig
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Kompletten Ergebnis-Datensatz einmalig per fetch() nachladen
|
||||||
|
* (php/controller/search-results-data.php, liest nur die Session-Daten,
|
||||||
|
* keine erneute Datenbank-Suche).
|
||||||
|
* 2. Sortieren + filtern (JS-Array).
|
||||||
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
||||||
|
*
|
||||||
|
* Ersetzt sorter.js, filter.js und paginator.js, deren Klick-Handler sich
|
||||||
|
* gegenseitig ins Gehege kamen.
|
||||||
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
||||||
|
* voll funktionsfähig.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
const state = {
|
||||||
|
allItems: [],
|
||||||
|
sort: 'alphabet',
|
||||||
|
category: 'all',
|
||||||
|
itemsPerPage: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let listContainer, sortRadios, categorySelect, limitSelect;
|
||||||
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
listContainer = document.querySelector('.s-res-list');
|
||||||
|
if (!listContainer) return;
|
||||||
|
|
||||||
|
sortRadios = document.querySelectorAll('.sort-radio');
|
||||||
|
categorySelect = document.getElementById('category-filter');
|
||||||
|
limitSelect = document.getElementById('s-res-per-page');
|
||||||
|
prevBtn = document.getElementById('prev-page-btn');
|
||||||
|
nextBtn = document.getElementById('next-page-btn');
|
||||||
|
numbersContainer = document.getElementById('dynamic-page-numbers');
|
||||||
|
jsNav = document.getElementById('js-page-navigation');
|
||||||
|
resultCountEl = document.getElementById('s-res-result-count');
|
||||||
|
|
||||||
|
const checkedRadio = document.querySelector('.sort-radio:checked');
|
||||||
|
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
|
||||||
|
state.category = categorySelect ? categorySelect.value : 'all';
|
||||||
|
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
|
||||||
|
|
||||||
|
fetchFullDataset()
|
||||||
|
.then(function (data) {
|
||||||
|
state.allItems = data.results || [];
|
||||||
|
attachEvents();
|
||||||
|
render();
|
||||||
|
if (jsNav) jsNav.style.display = 'flex';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
// JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
|
||||||
|
// (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
|
||||||
|
console.error('Suchergebnisse konnten nicht nachgeladen werden:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchFullDataset() {
|
||||||
|
return fetch('php/controller/search-results-data.php', { credentials: 'same-origin' })
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredSortedItems() {
|
||||||
|
let items = state.allItems;
|
||||||
|
|
||||||
|
if (state.category !== 'all') {
|
||||||
|
const wanted = state.category.toLowerCase();
|
||||||
|
items = items.filter(function (item) {
|
||||||
|
return (item.category || '').toLowerCase() === wanted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items = items.slice().sort(function (a, b) {
|
||||||
|
if (state.sort === 'likes') {
|
||||||
|
return (b.likes || 0) - (a.likes || 0);
|
||||||
|
}
|
||||||
|
if (state.sort === 'newest' || state.sort === 'oldest') {
|
||||||
|
return state.sort === 'newest' ? (b.id - a.id) : (a.id - b.id);
|
||||||
|
}
|
||||||
|
// alphabet (Standard)
|
||||||
|
return (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const items = getFilteredSortedItems();
|
||||||
|
|
||||||
|
if (resultCountEl) resultCountEl.textContent = items.length;
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(items.length / state.itemsPerPage));
|
||||||
|
if (state.currentPage > totalPages) state.currentPage = totalPages;
|
||||||
|
if (state.currentPage < 1) state.currentPage = 1;
|
||||||
|
|
||||||
|
const start = (state.currentPage - 1) * state.itemsPerPage;
|
||||||
|
const pageItems = items.slice(start, start + state.itemsPerPage);
|
||||||
|
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
if (pageItems.length === 0) {
|
||||||
|
const empty = document.createElement('p');
|
||||||
|
empty.textContent = 'Keine Beiträge gefunden.';
|
||||||
|
listContainer.appendChild(empty);
|
||||||
|
} else {
|
||||||
|
pageItems.forEach(function (item) {
|
||||||
|
listContainer.appendChild(buildCard(item));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaginatorUI(state.currentPage, totalPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCard(item) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 's-res-item';
|
||||||
|
card.setAttribute('data-likes', item.likes);
|
||||||
|
card.setAttribute('data-category', (item.category || '').toLowerCase());
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.className = 's-res-link';
|
||||||
|
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
|
||||||
|
link.textContent = item.title;
|
||||||
|
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.className = 's-res-item-title';
|
||||||
|
title.appendChild(link);
|
||||||
|
|
||||||
|
const authorName = document.createElement('span');
|
||||||
|
authorName.className = 's-res-author-name';
|
||||||
|
authorName.textContent = item.author;
|
||||||
|
|
||||||
|
const author = document.createElement('p');
|
||||||
|
author.className = 's-res-author';
|
||||||
|
author.append('Von: ', authorName);
|
||||||
|
|
||||||
|
const likes = document.createElement('span');
|
||||||
|
likes.className = 's-res-likes';
|
||||||
|
likes.textContent = '❤️ ' + item.likes;
|
||||||
|
|
||||||
|
const metaRow = document.createElement('div');
|
||||||
|
metaRow.className = 's-res-meta-row';
|
||||||
|
metaRow.append(author, likes);
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.className = 's-res-content';
|
||||||
|
content.append(title, metaRow);
|
||||||
|
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 's-res-arrow';
|
||||||
|
arrow.textContent = '→';
|
||||||
|
|
||||||
|
card.append(content, arrow);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginatorUI(currentPage, totalPages) {
|
||||||
|
if (!prevBtn || !nextBtn || !numbersContainer) return;
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage <= 1;
|
||||||
|
nextBtn.disabled = currentPage >= totalPages;
|
||||||
|
|
||||||
|
numbersContainer.innerHTML = '';
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 's-res-page-btn' + (i === currentPage ? ' s-res-page-btn-active' : '');
|
||||||
|
btn.textContent = i;
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
state.currentPage = i;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
numbersContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachEvents() {
|
||||||
|
sortRadios.forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function () {
|
||||||
|
state.sort = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (categorySelect) {
|
||||||
|
categorySelect.addEventListener('change', function () {
|
||||||
|
state.category = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitSelect) {
|
||||||
|
limitSelect.addEventListener('change', function () {
|
||||||
|
state.itemsPerPage = parseInt(this.value, 10) || 10;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevBtn) {
|
||||||
|
prevBtn.addEventListener('click', function () {
|
||||||
|
if (state.currentPage > 1) {
|
||||||
|
state.currentPage -= 1;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextBtn) {
|
||||||
|
nextBtn.addEventListener('click', function () {
|
||||||
|
state.currentPage += 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mit aktivem JS übernimmt render() Sortierung/Filter live
|
||||||
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
||||||
|
const filterForm = document.getElementById('s-res-filter-form');
|
||||||
|
if (filterForm) {
|
||||||
|
filterForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Übernimmt Sortierung, Suche (innerhalb der Kategorie) und Pagination der
|
||||||
|
* Kategorie-Ansicht vollständig clientseitig.
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Kompletten Kategorie-Datensatz einmalig per fetch() nachladen
|
||||||
|
* 2. Sortieren + nach Suchbegriff filtern (JS-Array).
|
||||||
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
||||||
|
*
|
||||||
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
||||||
|
* voll funktionsfähig
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
const state = {
|
||||||
|
allItems: [],
|
||||||
|
sort: 'alphabet',
|
||||||
|
query: '',
|
||||||
|
itemsPerPage: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let listContainer, sortRadios, searchInput, limitSelect;
|
||||||
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
||||||
|
let searchDebounceTimer;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
listContainer = document.querySelector('.s-res-list');
|
||||||
|
if (!listContainer) return;
|
||||||
|
|
||||||
|
sortRadios = document.querySelectorAll('.sort-radio');
|
||||||
|
searchInput = document.getElementById('cat-search-input');
|
||||||
|
limitSelect = document.getElementById('s-res-per-page');
|
||||||
|
prevBtn = document.getElementById('prev-page-btn');
|
||||||
|
nextBtn = document.getElementById('next-page-btn');
|
||||||
|
numbersContainer = document.getElementById('dynamic-page-numbers');
|
||||||
|
jsNav = document.getElementById('js-page-navigation');
|
||||||
|
resultCountEl = document.getElementById('s-res-result-count');
|
||||||
|
|
||||||
|
const checkedRadio = document.querySelector('.sort-radio:checked');
|
||||||
|
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
|
||||||
|
state.query = searchInput ? searchInput.value : '';
|
||||||
|
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
|
||||||
|
|
||||||
|
fetchFullDataset()
|
||||||
|
.then(function (data) {
|
||||||
|
state.allItems = data.results || [];
|
||||||
|
attachEvents();
|
||||||
|
render();
|
||||||
|
if (jsNav) jsNav.style.display = 'flex';
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
// JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
|
||||||
|
// (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
|
||||||
|
console.error('Kategorie-Beiträge konnten nicht nachgeladen werden:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchFullDataset() {
|
||||||
|
return fetch('php/controller/showCategory-data.php', { credentials: 'same-origin' })
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
return res.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredSortedItems() {
|
||||||
|
let items = state.allItems;
|
||||||
|
|
||||||
|
const needle = state.query.trim().toLowerCase();
|
||||||
|
if (needle !== '') {
|
||||||
|
items = items.filter(function (item) {
|
||||||
|
const haystack = ((item.title || '') + ' ' + (item.content || '')).toLowerCase();
|
||||||
|
return haystack.indexOf(needle) !== -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items = items.slice().sort(function (a, b) {
|
||||||
|
if (state.sort === 'likes') {
|
||||||
|
return (b.likes || 0) - (a.likes || 0);
|
||||||
|
}
|
||||||
|
if (state.sort === 'newest' || state.sort === 'oldest') {
|
||||||
|
return state.sort === 'newest' ? (b.id - a.id) : (a.id - b.id);
|
||||||
|
}
|
||||||
|
// alphabet (Standard)
|
||||||
|
return (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const items = getFilteredSortedItems();
|
||||||
|
|
||||||
|
if (resultCountEl) resultCountEl.textContent = items.length;
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(items.length / state.itemsPerPage));
|
||||||
|
if (state.currentPage > totalPages) state.currentPage = totalPages;
|
||||||
|
if (state.currentPage < 1) state.currentPage = 1;
|
||||||
|
|
||||||
|
const start = (state.currentPage - 1) * state.itemsPerPage;
|
||||||
|
const pageItems = items.slice(start, start + state.itemsPerPage);
|
||||||
|
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
if (pageItems.length === 0) {
|
||||||
|
const empty = document.createElement('p');
|
||||||
|
empty.textContent = 'Keine Beiträge gefunden.';
|
||||||
|
listContainer.appendChild(empty);
|
||||||
|
} else {
|
||||||
|
pageItems.forEach(function (item) {
|
||||||
|
listContainer.appendChild(buildCard(item));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePaginatorUI(state.currentPage, totalPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCard(item) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 's-res-item';
|
||||||
|
card.setAttribute('data-likes', item.likes);
|
||||||
|
card.setAttribute('data-category', (item.category || '').toLowerCase());
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.className = 's-res-link';
|
||||||
|
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
|
||||||
|
link.textContent = item.title;
|
||||||
|
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.className = 's-res-item-title';
|
||||||
|
title.appendChild(link);
|
||||||
|
|
||||||
|
const authorName = document.createElement('span');
|
||||||
|
authorName.className = 's-res-author-name';
|
||||||
|
authorName.textContent = item.author;
|
||||||
|
|
||||||
|
const author = document.createElement('p');
|
||||||
|
author.className = 's-res-author';
|
||||||
|
author.append('Von: ', authorName);
|
||||||
|
|
||||||
|
const likes = document.createElement('span');
|
||||||
|
likes.className = 's-res-likes';
|
||||||
|
likes.textContent = '❤️ ' + item.likes;
|
||||||
|
|
||||||
|
const metaRow = document.createElement('div');
|
||||||
|
metaRow.className = 's-res-meta-row';
|
||||||
|
metaRow.append(author, likes);
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.className = 's-res-content';
|
||||||
|
content.append(title, metaRow);
|
||||||
|
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 's-res-arrow';
|
||||||
|
arrow.textContent = '→';
|
||||||
|
|
||||||
|
card.append(content, arrow);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginatorUI(currentPage, totalPages) {
|
||||||
|
if (!prevBtn || !nextBtn || !numbersContainer) return;
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage <= 1;
|
||||||
|
nextBtn.disabled = currentPage >= totalPages;
|
||||||
|
|
||||||
|
numbersContainer.innerHTML = '';
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 's-res-page-btn' + (i === currentPage ? ' s-res-page-btn-active' : '');
|
||||||
|
btn.textContent = i;
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
state.currentPage = i;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
numbersContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachEvents() {
|
||||||
|
sortRadios.forEach(function (radio) {
|
||||||
|
radio.addEventListener('change', function () {
|
||||||
|
state.sort = this.value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
// Live-Filterung während des Tippens (leicht entprellt), da der
|
||||||
|
// Datensatz bereits vollständig im Browser liegt.
|
||||||
|
searchInput.addEventListener('input', function () {
|
||||||
|
clearTimeout(searchDebounceTimer);
|
||||||
|
const value = this.value;
|
||||||
|
searchDebounceTimer = setTimeout(function () {
|
||||||
|
state.query = value;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitSelect) {
|
||||||
|
limitSelect.addEventListener('change', function () {
|
||||||
|
state.itemsPerPage = parseInt(this.value, 10) || 10;
|
||||||
|
state.currentPage = 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevBtn) {
|
||||||
|
prevBtn.addEventListener('click', function () {
|
||||||
|
if (state.currentPage > 1) {
|
||||||
|
state.currentPage -= 1;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextBtn) {
|
||||||
|
nextBtn.addEventListener('click', function () {
|
||||||
|
state.currentPage += 1;
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mit aktivem JS übernimmt render() Sortierung/Suche live
|
||||||
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
||||||
|
const filterForm = document.getElementById('cat-filter-form');
|
||||||
|
if (filterForm) {
|
||||||
|
filterForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
|
require_once "../model/CommentManager.php";
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user_email"])) {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Du musst angemeldet sein, um zu kommentieren."
|
||||||
|
]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$articleId = $_POST["article_id"] ?? null;
|
||||||
|
$content = trim($_POST["content"] ?? "");
|
||||||
|
$parentCommentId = $_POST["parent_comment_id"] ?? null;
|
||||||
|
|
||||||
|
if ($parentCommentId === "" || $parentCommentId === "0") {
|
||||||
|
$parentCommentId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($articleId) || empty($content)) {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Kommentar darf nicht leer sein."
|
||||||
|
]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
|
||||||
|
$commentId = $commentManager->addComment(
|
||||||
|
$articleId,
|
||||||
|
$_SESSION["user_email"],
|
||||||
|
$content,
|
||||||
|
$parentCommentId
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"commentId" => $commentId,
|
||||||
|
"author" => $_SESSION["user_email"],
|
||||||
|
"content" => $content,
|
||||||
|
"created" => date("Y-m-d H:i:s"),
|
||||||
|
"parentCommentId" => $parentCommentId
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Kommentar konnte nicht gespeichert werden."
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Verarbeitung der Passwortbestätigung
|
||||||
|
Funktion: Liest die zwischengespeicherten Passwortdaten aus,
|
||||||
|
aktualisiert das Passwort des Benutzers und gibt das Ergebnis
|
||||||
|
für die View zurück.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = "Passwort zurücksetzen";
|
||||||
|
$message = "";
|
||||||
|
$link = "";
|
||||||
|
$isSuccess = false;
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = "data/pending-password/" . $token . ".json";
|
||||||
|
|
||||||
|
if (empty($token) || !file_exists($file)) {
|
||||||
|
|
||||||
|
$message = "Der Bestätigungslink ist ungültig oder bereits abgelaufen.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
|
||||||
|
if ($data === null || empty($data["email"]) || empty($data["password"])) {
|
||||||
|
|
||||||
|
$message = "Die Daten zur Passwortänderung konnten nicht gelesen werden.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
$user = $dao->findUser($data["email"]);
|
||||||
|
|
||||||
|
if ($user !== null) {
|
||||||
|
|
||||||
|
$dao->updateUser(
|
||||||
|
$user["email"],
|
||||||
|
$user["email"],
|
||||||
|
$user["vorname"],
|
||||||
|
$user["nachname"],
|
||||||
|
$data["password"]
|
||||||
|
);
|
||||||
|
|
||||||
|
$_SESSION["message"] = "password_changed";
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$message = "Der Benutzer konnte nicht gefunden werden.";
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($file);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
$message = "Das Passwort konnte nicht geändert werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Verarbeitung der Registrierungsbestätigung
|
||||||
|
Funktion: Liest die zwischengespeicherten Registrierungsdaten aus,
|
||||||
|
legt den Benutzer an und gibt das Ergebnis für die View zurück.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = "Registrierung";
|
||||||
|
$message = "";
|
||||||
|
$link = "";
|
||||||
|
$isSuccess = false;
|
||||||
|
|
||||||
|
$token = basename($_GET["token"] ?? "");
|
||||||
|
$file = "data/pending/" . $token . ".json";
|
||||||
|
|
||||||
|
if (empty($token) || !file_exists($file)) {
|
||||||
|
|
||||||
|
$message = "Der Registrierungslink ist ungültig oder bereits abgelaufen.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents($file), true);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$data === null ||
|
||||||
|
empty($data["email"]) ||
|
||||||
|
empty($data["vorname"]) ||
|
||||||
|
empty($data["nachname"]) ||
|
||||||
|
empty($data["password"])
|
||||||
|
) {
|
||||||
|
|
||||||
|
$message = "Die Registrierungsdaten konnten nicht gelesen werden.";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
if ($dao->findUser($data["email"]) === null) {
|
||||||
|
|
||||||
|
$dao->addUser(
|
||||||
|
$data["email"],
|
||||||
|
$data["vorname"],
|
||||||
|
$data["nachname"],
|
||||||
|
$data["password"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($file);
|
||||||
|
|
||||||
|
$_SESSION["message"] = "registration_confirmed";
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
|
||||||
|
$message = "Die Registrierung konnte nicht abgeschlossen werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
SESSION_START();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
require_once '../model/LocalArticleManager.php';
|
require_once '../model/LocalArticleManager.php';
|
||||||
require_once '../model/ArticleManager.php';
|
require_once '../model/ArticleManager.php';
|
||||||
require_once '../validator/article-validator.php';
|
require_once '../validator/article-validator.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
||||||
$_SESSION["old_content"] = $_POST["content"] ?? '';
|
$_SESSION["old_content"] = $_POST["content"] ?? '';
|
||||||
@@ -22,12 +28,6 @@ require_once '../validator/article-validator.php';
|
|||||||
$tags = $_POST['tags'] ?? '';
|
$tags = $_POST['tags'] ?? '';
|
||||||
|
|
||||||
// -------------------------------- Validierung der Daten: -------------------------
|
// -------------------------------- Validierung der Daten: -------------------------
|
||||||
if (!articleAuthorValidator($author)) {
|
|
||||||
$_SESSION["message"] = "author_not_valid";
|
|
||||||
header("location: ../../index.php?pfad=createArticle");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!articleTitleValidator($title)) {
|
if (!articleTitleValidator($title)) {
|
||||||
$_SESSION["message"] = "invalid_title";
|
$_SESSION["message"] = "invalid_title";
|
||||||
header("location: ../../index.php?pfad=createArticle");
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
@@ -62,6 +62,59 @@ require_once '../validator/article-validator.php';
|
|||||||
$cleanedTags = array_unique($cleanedTags);
|
$cleanedTags = array_unique($cleanedTags);
|
||||||
$cleanedTags = implode(',', $cleanedTags);
|
$cleanedTags = implode(',', $cleanedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------- Base64-Bilder verarbeiten und auf Server speichern -----------------
|
||||||
|
$blocks = json_decode($content, true);
|
||||||
|
$uploadDir = __DIR__ . '/../../uploads/';
|
||||||
|
|
||||||
|
if (!file_exists($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($blocks)) {
|
||||||
|
foreach ($blocks as &$block) {
|
||||||
|
// sicherstellen, dass 'type' und 'value' existieren:
|
||||||
|
if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && str_starts_with($block['value'], 'data:image/')) {
|
||||||
|
|
||||||
|
// Base64-String zerlegen
|
||||||
|
$parts = explode(',', $block['value']);
|
||||||
|
|
||||||
|
// falls der String korrupt ist und kein Komma hat
|
||||||
|
if (count($parts) < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = $parts[0];
|
||||||
|
$base64Data = $parts[1];
|
||||||
|
|
||||||
|
// Dateiendung ermitteln
|
||||||
|
preg_match('/data:image\/(?<extension>.*?);/', $metadata, $matches);
|
||||||
|
$extension = $matches['extension'] ?? 'jpg';
|
||||||
|
if ($extension === 'jpeg') {
|
||||||
|
$extension = 'jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eindeutigen Dateinamen generieren
|
||||||
|
$fileName = 'img_' . uniqid() . '.' . $extension;
|
||||||
|
$filePath = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
// Datei im /uploads speichern:
|
||||||
|
if (file_put_contents($filePath, base64_decode($base64Data)) !== false) {
|
||||||
|
// temporären Base64-String durch den echten Pfad ersetzen
|
||||||
|
$block['value'] = 'uploads/' . $fileName;
|
||||||
|
} else {
|
||||||
|
$_SESSION["message"] = "image_upload_error";
|
||||||
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiertes Array wieder in JSON konvertieren
|
||||||
|
$finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||||
try {
|
try {
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
@@ -70,7 +123,7 @@ require_once '../validator/article-validator.php';
|
|||||||
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
|
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
|
||||||
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
||||||
|
|
||||||
} catch (Exception $e){
|
} catch (\Throwable $e){
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
header("location: ../../index.php?pfad=createArticle");
|
header("location: ../../index.php?pfad=createArticle");
|
||||||
exit();
|
exit();
|
||||||
@@ -82,7 +135,4 @@ require_once '../validator/article-validator.php';
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
require_once "php/model/UserManager.php";
|
require_once __DIR__ . "/../model/UserManager.php";
|
||||||
|
require_once __DIR__ . "/../model/ArticleManager.php";
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Deregistrierung
|
Deregistrierung
|
||||||
@@ -10,21 +19,30 @@ require_once "php/model/UserManager.php";
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
$dao = UserManager::getInstance();
|
$dao = UserManager::getInstance();
|
||||||
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
|
||||||
if (isset($_SESSION["user_email"])) {
|
if (isset($_SESSION["user_email"])) {
|
||||||
$dao->deleteUser($_SESSION["user_email"]);
|
$dao->deleteUser($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
$articles = $articleManager->getArticlesByAuthor($_SESSION["user_email"]);
|
||||||
|
foreach ($articles as $article) {
|
||||||
|
$articleManager->deleteArticle($article->getId(), $_SESSION["user_email"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException("missing_user_email");
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
session_destroy();
|
session_destroy();
|
||||||
|
|
||||||
header("Location: index.php");
|
header("Location: ../../index.php");
|
||||||
exit();
|
exit();
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
|
|
||||||
header("Location: index.php?pfad=profile");
|
header("Location: ../../index.php?pfad=profile");
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,11 @@ if (session_status() === PHP_SESSION_NONE) {
|
|||||||
|
|
||||||
require_once __DIR__ . "/../model/ArticleManager.php";
|
require_once __DIR__ . "/../model/ArticleManager.php";
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
if (isset($_SESSION["user_email"])) {
|
if (isset($_SESSION["user_email"])) {
|
||||||
@@ -13,7 +18,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
session_destroy();
|
session_destroy();
|
||||||
|
|
||||||
header("Location: index.php");
|
header("Location: ../../index.php");
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
require_once 'php/model/Article.php';
|
require_once 'php/model/Article.php';
|
||||||
require_once 'php/model/ArticleManager.php';
|
require_once 'php/model/ArticleManager.php';
|
||||||
require_once 'php/model/LocalArticleManager.php';
|
require_once 'php/model/LocalArticleManager.php';
|
||||||
|
|
||||||
|
$categoriesWithArticles = [];
|
||||||
|
|
||||||
|
$allowedCategories = [
|
||||||
|
'deutsch', 'englisch', 'franzoesisch', 'latein', 'literatur',
|
||||||
|
'mathe', 'biologie', 'chemie', 'physik', 'informatik', 'astronomie',
|
||||||
|
'geschichte', 'erdkunde', 'sozialkunde', 'wirtschaft', 'religion',
|
||||||
|
'ethik', 'philosophie', 'psychologie', 'kunst', 'musik', 'theater',
|
||||||
|
'technik', 'werken', 'hauswirtschaft', 'sport'
|
||||||
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
// Beziehen der Dummy-Beiträge aus dem ArticleManager:
|
|
||||||
$dummy1 = $articleManager->getArticle(1);
|
foreach ($allowedCategories as $categorySlug) {
|
||||||
$dummy2 = $articleManager->getArticle(2);
|
$allCategoryArticles = $articleManager->getArticlesByCategory($categorySlug);
|
||||||
$dummy3 = $articleManager->getArticle(3);
|
|
||||||
|
// nur Kategorien, die Beiträge enthält:
|
||||||
|
if (!empty($allCategoryArticles) && is_array($allCategoryArticles)) {
|
||||||
|
|
||||||
|
// Beiträge nach Erstellungsdatum sortieren:
|
||||||
|
usort($allCategoryArticles, function($a, $b) {
|
||||||
|
$dateA = strtotime($a->getCreationDate());
|
||||||
|
$dateB = strtotime($b->getCreationDate());
|
||||||
|
return $dateB <=> $dateA; // Absteigende Sortierung
|
||||||
|
});
|
||||||
|
|
||||||
|
// auf die 5 zuletzt hinzugefügten Beiträge begrenzen:
|
||||||
|
$limitedArticles = array_slice($allCategoryArticles, 0, 5);
|
||||||
|
|
||||||
|
$categoriesWithArticles[] = [
|
||||||
|
'slug' => $categorySlug,
|
||||||
|
'name' => ucfirst($categorySlug), // Erster Buchstabe groß für die Ansicht
|
||||||
|
'articles' => $limitedArticles
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception $e){
|
} catch (Exception $e){
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
echo "Fehler aufgetreten: " . $e->getMessage();
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "php/model/UserManager.php";
|
||||||
|
require_once "php/validator/user-validator.php";
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
|
$email = trim($_POST["email"] ?? "");
|
||||||
|
$plainPassword = $_POST["password"] ?? "";
|
||||||
|
|
||||||
|
if (!userEmailValidator($email)) {
|
||||||
|
$error = "Bitte gib eine gültige E-Mail-Adresse ein.";
|
||||||
|
} elseif (!userPasswordValidator($plainPassword)) {
|
||||||
|
$error = "Das Passwort muss 5 bis 12 Zeichen lang sein.";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
$existingUser = $dao->findUser($email);
|
||||||
|
|
||||||
|
if (!is_dir("data/mails") && !mkdir("data/mails", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/mails konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir("data/pending-password") && !mkdir("data/pending-password", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/pending-password konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/mails") || !is_writable("data/pending-password")) {
|
||||||
|
throw new RuntimeException("Ordner sind nicht beschreibbar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser !== null) {
|
||||||
|
$pendingData = [
|
||||||
|
"email" => $email,
|
||||||
|
"password" => $plainPassword
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
"data/pending-password/" . $token . ".json",
|
||||||
|
json_encode($pendingData, JSON_PRETTY_PRINT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser !== null) {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Passwort zurücksetzen</h2>
|
||||||
|
<p>Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=confirm-password&token=$token'>
|
||||||
|
Passwortänderung bestätigen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Passwort zurücksetzen</h2>
|
||||||
|
<p>Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.</p>
|
||||||
|
<p>Für diese E-Mail-Adresse wurde kein Konto gefunden.</p>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents("data/mails/" . $token . ".html", $mailContent);
|
||||||
|
|
||||||
|
$success = 'Weitere Infos finden Sie in der Datei
|
||||||
|
<a href="index.php?pfad=show-mail&token=' . htmlspecialchars($token) . '" target="_blank">xy</a>.';
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Die Passwortänderung konnte nicht verarbeitet werden.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
require_once "php/model/UserManager.php";
|
require_once "php/model/UserManager.php";
|
||||||
require_once "php/model/Article.php";
|
require_once "php/model/Article.php";
|
||||||
require_once "php/model/ArticleManager.php";
|
require_once "php/model/ArticleManager.php";
|
||||||
|
require_once "php/model/CommentManager.php";
|
||||||
require_once "php/validator/user-validator.php";
|
require_once "php/validator/user-validator.php";
|
||||||
|
|
||||||
$error = null;
|
$error = null;
|
||||||
@@ -62,6 +63,7 @@ try {
|
|||||||
$_SESSION["user"] = $vorname . " " . $nachname;
|
$_SESSION["user"] = $vorname . " " . $nachname;
|
||||||
$_SESSION["user_email"] = $newEmail;
|
$_SESSION["user_email"] = $newEmail;
|
||||||
|
|
||||||
|
$_SESSION["message"] = "profile_updated";
|
||||||
header("Location: index.php?pfad=profile");
|
header("Location: index.php?pfad=profile");
|
||||||
exit();
|
exit();
|
||||||
} else {
|
} else {
|
||||||
@@ -76,6 +78,9 @@ try {
|
|||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
$userArticles = $articleManager->getArticlesByAuthor($_SESSION["user_email"]);
|
$userArticles = $articleManager->getArticlesByAuthor($_SESSION["user_email"]);
|
||||||
|
|
||||||
|
$commentManager = CommentManager::getInstance();
|
||||||
|
$userComments = $commentManager->getCommentsByAuthor($_SESSION["user_email"]);
|
||||||
|
|
||||||
if (!isset($userArticles)) {
|
if (!isset($userArticles)) {
|
||||||
$_SESSION["message"] = "user_has_no_articles";
|
$_SESSION["message"] = "user_has_no_articles";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ require_once "php/model/UserManager.php";
|
|||||||
require_once "php/validator/user-validator.php";
|
require_once "php/validator/user-validator.php";
|
||||||
|
|
||||||
$error = null;
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
|
||||||
@@ -24,20 +25,71 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
try {
|
try {
|
||||||
$dao = UserManager::getInstance();
|
$dao = UserManager::getInstance();
|
||||||
|
|
||||||
|
// Token für die simulierte E-Mail und die spätere Bestätigung erzeugen.
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
$existingUser = $dao->findUser($email);
|
||||||
|
|
||||||
|
if (!is_dir("data/mails") && !mkdir("data/mails", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/mails konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir("data/pending") && !mkdir("data/pending", 0777, true)) {
|
||||||
|
throw new RuntimeException("Ordner data/pending konnte nicht erstellt werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/mails")) {
|
||||||
|
throw new RuntimeException("Ordner data/mails ist nicht beschreibbar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable("data/pending")) {
|
||||||
|
throw new RuntimeException("Ordner data/pending ist nicht beschreibbar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingUser === null) {
|
||||||
$password = password_hash($plainPassword, PASSWORD_DEFAULT);
|
$password = password_hash($plainPassword, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
$dao->addUser($email, $vorname, $nachname, $password);
|
$pendingData = [
|
||||||
|
"email" => $email,
|
||||||
|
"vorname" => $vorname,
|
||||||
|
"nachname" => $nachname,
|
||||||
|
"password" => $password
|
||||||
|
];
|
||||||
|
|
||||||
$_SESSION["user"] = $vorname . " " . $nachname;
|
file_put_contents(
|
||||||
$_SESSION["user_email"] = $email;
|
"data/pending/" . $token . ".json",
|
||||||
|
json_encode($pendingData, JSON_PRETTY_PRINT)
|
||||||
|
);
|
||||||
|
|
||||||
header("Location: index.php");
|
$mailContent = "
|
||||||
exit();
|
<h2>Registrierung bestätigen</h2>
|
||||||
|
<p>Bitte ignorieren Sie diese Nachricht, wenn Sie sich nicht registrieren wollten.</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=confirm-register&token=$token'>
|
||||||
|
Registrierung bestätigen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$mailContent = "
|
||||||
|
<h2>Registrierung</h2>
|
||||||
|
<p>Bitte ignorieren Sie diese Nachricht, wenn Sie sich nicht registrieren wollten.</p>
|
||||||
|
<p>Sie sind bereits registriert.</p>
|
||||||
|
<p>
|
||||||
|
<a href='index.php?pfad=password-forgotten'>
|
||||||
|
Passwort vergessen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents("data/mails/" . $token . ".html", $mailContent);
|
||||||
|
|
||||||
|
// Neutrale Meldung, damit nicht sichtbar wird, ob die E-Mail bereits registriert ist.
|
||||||
|
$success = 'Weitere Infos finden Sie in der Datei
|
||||||
|
<a href="index.php?pfad=show-mail&token=' . htmlspecialchars($token) . '" target="_blank">xy</a>.';
|
||||||
|
|
||||||
} catch (InvalidArgumentException $e) {
|
|
||||||
$error = $e->getMessage();
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$error = "Die Registrierung konnte nicht gespeichert werden.";
|
$error = "Die Registrierung konnte nicht verarbeitet werden.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,54 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
require_once '../model/LocalArticleManager.php';
|
session_start();
|
||||||
|
}
|
||||||
require_once '../model/ArticleManager.php';
|
require_once '../model/ArticleManager.php';
|
||||||
|
require_once '../model/UserManager.php';
|
||||||
require_once '../model/Article.php';
|
require_once '../model/Article.php';
|
||||||
|
require_once '../validator/search-validator.php';
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
|
if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
|
||||||
|
|
||||||
$search = trim($_GET["q"]);
|
$search = trim($_GET["q"]);
|
||||||
if (empty($search)) {
|
if (!searchQueryValidator($search)) {
|
||||||
$_SESSION["search_results"] = [];
|
$_SESSION["search_results"] = [];
|
||||||
$_SESSION["search_query"] = "";
|
$_SESSION["search_query"] = "";
|
||||||
$_SESSION["message"] = "missing_parameters";
|
$_SESSION["message"] = "invalid_search_query";
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
|
||||||
$results = $articleManager->search($search);
|
$results = $articleManager->search($search);
|
||||||
|
|
||||||
$sortStyle = $_GET['sort'] ?? 'alphabet';
|
|
||||||
$_SESSION['search_sort'] = $sortStyle;
|
|
||||||
|
|
||||||
if ($sortStyle === 'alphabet') {
|
|
||||||
// Titel aufsteigend alphabetiisch sortiert
|
|
||||||
usort($results, function($a, $b) {
|
|
||||||
return strcasecmp($a->title, $b->title);
|
|
||||||
});
|
|
||||||
} elseif ($sortStyle === 'newest') {
|
|
||||||
// Datum neu zu alt sortiert
|
|
||||||
usort($results, function($a, $b) {
|
|
||||||
return strcmp($b->creationDate, $a->creationDate);
|
|
||||||
});
|
|
||||||
} elseif ($sortStyle === 'oldest') {
|
|
||||||
// Datum alt zu neu sortiert
|
|
||||||
usort($results, function($a, $b) {
|
|
||||||
return strcmp($a->creationDate, $b->creationDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ergebnisse werden in ein flaches array umgewandelt, da sont incomplete-PHP error im Ergebnis
|
|
||||||
$safeArrayResults = [];
|
$safeArrayResults = [];
|
||||||
foreach ($results as $obj) {
|
foreach ($results as $obj) {
|
||||||
$safeArrayResults[] = [
|
$safeArrayResults[] = [
|
||||||
"id" => $obj->id,
|
"id" => $obj->getId(),
|
||||||
"title" => $obj->title,
|
"title" => $obj->getTitle(),
|
||||||
"content" => $obj->content,
|
"content" => $obj->getContent(),
|
||||||
"author" => $obj->author,
|
"author" => $userManager->findUser($obj->getAuthor())["vorname"] . " " . $userManager->findUser($obj->getAuthor())["nachname"],
|
||||||
"category" => $obj->category,
|
"category" => $obj->getCategory(),
|
||||||
"tags" => $obj->tags,
|
"tags" => $obj->getTags(),
|
||||||
"creationDate" => $obj->creationDate
|
"creationDate" => $obj->getCreationDate(),
|
||||||
|
"likes" => $obj->getLikes(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,13 +40,25 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
|
|||||||
$_SESSION["search_query"] = $search;
|
$_SESSION["search_query"] = $search;
|
||||||
$_SESSION["message"] = "new_search_results";
|
$_SESSION["message"] = "new_search_results";
|
||||||
|
|
||||||
} catch (Exception $e){
|
} catch (Exception $e) {
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header("Location: ../../index.php?pfad=search-results");
|
|
||||||
|
$sort = $_GET['sort'] ?? 'alphabet';
|
||||||
|
$_SESSION['search_sort'] = $sort;
|
||||||
|
|
||||||
|
$category = $_GET['category'] ?? 'all';
|
||||||
|
$_SESSION['search_category'] = $category;
|
||||||
|
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
|
||||||
|
if (!searchLimitValidator($limit)) {
|
||||||
|
$limit = 10;
|
||||||
|
}
|
||||||
|
$_SESSION['search_limit'] = $limit;
|
||||||
|
|
||||||
|
// Neue Suche -> immer wieder bei Seite 1 beginnen
|
||||||
|
header("Location: ../../index.php?pfad=search-results&q=" . urlencode($search) . "&sort=" . urlencode($sort) . "&category=" . urlencode($category) . "&limit=" . $limit . "&page=1");
|
||||||
exit();
|
exit();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Liefert den vollständigen, zuletzt gefundenen Ergebnis-Datensatz als JSON.
|
||||||
|
*
|
||||||
|
* Wird von js/search-results.js EINMALIG per fetch() geladen, damit Sortierung,
|
||||||
|
* Filterung und Pagination danach komplett im Browser laufen können, ohne
|
||||||
|
* weitere Serveranfragen oder eine erneute Datenbank-Suche auszulösen.
|
||||||
|
*/
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['search_results'] ?? [];
|
||||||
|
|
||||||
|
$payload = array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
|
'title' => $item['title'],
|
||||||
|
'author' => $item['author'],
|
||||||
|
'category' => $item['category'] ?? '',
|
||||||
|
'likes' => getLikeCount($item),
|
||||||
|
];
|
||||||
|
}, $rawResults);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'query' => $_SESSION['search_query'] ?? '',
|
||||||
|
'results' => array_values($payload),
|
||||||
|
]);
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
require_once 'php/model/Article.php';
|
require_once 'php/model/Article.php';
|
||||||
require_once 'php/model/ArticleManager.php';
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once 'php/model/UserManager.php';
|
||||||
|
require_once 'php/model/CommentManager.php';
|
||||||
|
|
||||||
if (isset($_GET["id"]) && !empty($_GET["id"])){
|
if (isset($_GET["id"]) && !empty($_GET["id"])){
|
||||||
try {
|
try {
|
||||||
@@ -14,11 +19,36 @@ if (isset($_GET["id"]) && !empty($_GET["id"])){
|
|||||||
$category = $article->getCategory();
|
$category = $article->getCategory();
|
||||||
$author = $article->getAuthor();
|
$author = $article->getAuthor();
|
||||||
$tags = $article->getTags();
|
$tags = $article->getTags();
|
||||||
|
$articleObj = $article; // Objekt für die Like-Abfagen sichern
|
||||||
|
|
||||||
|
$userManager = UserManager::getInstance();
|
||||||
|
$name = $userManager->findUser($author)["vorname"]." ".$userManager->findUser($author)["nachname"];
|
||||||
}else{
|
}else{
|
||||||
//header("location: index.php?pfad=404");
|
//header("location: index.php?pfad=404");
|
||||||
include_once "content/404.php";
|
include_once "content/404.php";
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($_GET["pfad"] == "updateArticle"){
|
||||||
|
if($article->getAuthor() != $_SESSION["user_email"]){
|
||||||
|
$_SESSION["message"] = "unauthorized_access";
|
||||||
|
header("location: index.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){
|
} catch (Exception $e){
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
exit();
|
exit();
|
||||||
|
|||||||
@@ -1,21 +1,52 @@
|
|||||||
<?php
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
require_once 'php/model/Article.php';
|
require_once 'php/model/Article.php';
|
||||||
require_once 'php/model/ArticleManager.php';
|
require_once 'php/model/ArticleManager.php';
|
||||||
|
require_once 'php/model/UserManager.php';
|
||||||
require_once 'php/validator/article-validator.php';
|
require_once 'php/validator/article-validator.php';
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
if (isset($_GET["category"]) && !empty($_GET["category"]) && articleCategoryValidator($_GET["category"])) {
|
||||||
|
|
||||||
if (isset($_GET["category"]) && !empty($_GET["category"]) && articleCategoryValidator($_GET["category"])){
|
|
||||||
$category = $_GET["category"];
|
$category = $_GET["category"];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
$articles = $articleManager->getArticlesByCategory($category);
|
$userManager = UserManager::getInstance();
|
||||||
|
|
||||||
|
$allArticles = $articleManager->getArticlesByCategory($category);
|
||||||
|
|
||||||
|
$safeArrayResults = [];
|
||||||
|
foreach ($allArticles as $article) {
|
||||||
|
$authorName = $userManager->findUser($article->getAuthor())["vorname"] . " " . $userManager->findUser($article->getAuthor())["nachname"];
|
||||||
|
$safeArrayResults[] = [
|
||||||
|
"id" => $article->getID(),
|
||||||
|
"title" => $article->getTitle(),
|
||||||
|
"content" => $article->getContent(),
|
||||||
|
"author" => $authorName,
|
||||||
|
"category" => $article->getCategory(),
|
||||||
|
"tags" => $article->getTags(),
|
||||||
|
"creationDate" => $article->getCreationDate(),
|
||||||
|
"likes" => $article->getLikes(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session dient hier (wie bei search-results) als Zwischenspeicher, damit
|
||||||
|
// js/showCategory.js den kompletten Datensatz per showCategory-data.php
|
||||||
|
// nachladen kann, ohne die Kategorie-Abfrage ein zweites Mal auszuführen.
|
||||||
|
$_SESSION['category_results'] = $safeArrayResults;
|
||||||
|
$_SESSION['category_name'] = $category;
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$_SESSION["message"] = "internal_error";
|
$_SESSION["message"] = "internal_error";
|
||||||
include_once "content/404.php";
|
include_once "content/404.php";
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
|
} else {
|
||||||
$_SESSION["message"] = "invalid_category";
|
$_SESSION["message"] = "invalid_category";
|
||||||
include_once "content/404.php";
|
include_once "content/404.php";
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Liefert den vollständigen, zuletzt geladenen Kategorie-Datensatz als JSON.
|
||||||
|
*
|
||||||
|
* Wird von js/showCategory.js EINMALIG per fetch() geladen, damit Sortierung,
|
||||||
|
* Suche und Pagination danach komplett im Browser laufen können, ohne
|
||||||
|
* weitere Serveranfragen oder eine erneute Datenbank-Abfrage auszulösen
|
||||||
|
*
|
||||||
|
* Analog zu php/controller/search-results-data.php
|
||||||
|
*/
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../includes/resultsHelper.php';
|
||||||
|
|
||||||
|
$rawResults = $_SESSION['category_results'] ?? [];
|
||||||
|
|
||||||
|
$payload = array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
|
'title' => $item['title'],
|
||||||
|
'content' => $item['content'] ?? '',
|
||||||
|
'author' => $item['author'],
|
||||||
|
'category' => $item['category'] ?? '',
|
||||||
|
'likes' => getLikeCount($item),
|
||||||
|
];
|
||||||
|
}, $rawResults);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'category' => $_SESSION['category_name'] ?? '',
|
||||||
|
'results' => array_values($payload),
|
||||||
|
]);
|
||||||
@@ -1,14 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
SESSION_START();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
require_once '../model/LocalArticleManager.php';
|
require_once '../model/LocalArticleManager.php';
|
||||||
require_once '../model/ArticleManager.php';
|
require_once '../model/ArticleManager.php';
|
||||||
require_once '../model/Article.php';
|
require_once '../model/Article.php';
|
||||||
require_once '../validator/article-validator.php';
|
require_once '../validator/article-validator.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION["user_email"])) {
|
||||||
|
header("Location: index.php?pfad=login");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
||||||
$_SESSION["old_content"] = $_POST["content"] ?? '';
|
$_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"] ?? '';
|
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
|
||||||
|
|
||||||
if (isset($_GET["id"]) && !empty($_GET["id"])) {
|
if (isset($_GET["id"]) && !empty($_GET["id"])) {
|
||||||
@@ -19,6 +27,20 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
exit();
|
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"])){
|
if (!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
|
||||||
$_SESSION["message"] = "missing_parameters";
|
$_SESSION["message"] = "missing_parameters";
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
@@ -31,12 +53,6 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
$tags = $_POST['tags'] ?? '';
|
$tags = $_POST['tags'] ?? '';
|
||||||
|
|
||||||
// -------------------------------- Validierung der Daten: -------------------------
|
// -------------------------------- Validierung der Daten: -------------------------
|
||||||
if (!articleAuthorValidator($author)) {
|
|
||||||
$_SESSION["message"] = "author_not_valid";
|
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!articleTitleValidator($title)) {
|
if (!articleTitleValidator($title)) {
|
||||||
$_SESSION["message"] = "invalid_title";
|
$_SESSION["message"] = "invalid_title";
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
@@ -72,16 +88,91 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
$cleanedTags = implode(',', $cleanedTags);
|
$cleanedTags = implode(',', $cleanedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------- Base64-Bilder speichern ---------------------------------------------
|
||||||
|
$blocks = json_decode($content, true);
|
||||||
|
$uploadDir = __DIR__ . '/../../uploads/';
|
||||||
|
|
||||||
|
if (!file_exists($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Gelöschte Bilder über die JS-Löschliste entfernen ----------------- TODO: Gelöschte Bilder über die JS-Löschliste entfernen
|
||||||
|
/*if (isset($_POST['deleted_images'])) {
|
||||||
|
$deletedImages = json_decode($_POST['deleted_images'], true);
|
||||||
|
|
||||||
|
// Wir ermitteln den physisch echten, absoluten Pfad zum uploads-Ordner auf der Festplatte
|
||||||
|
$uploadDir = realpath(__DIR__ . '/../../uploads') . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
if (is_array($deletedImages)) {
|
||||||
|
foreach ($deletedImages as $imagePath) {
|
||||||
|
// Nur den reinen Dateinamen heraustrennen (z.B. img_65a123.jpg)
|
||||||
|
$filename = basename($imagePath);
|
||||||
|
$fullDeletePath = $uploadDir . $filename;
|
||||||
|
|
||||||
|
// Debugging & Löschen:
|
||||||
|
if (file_exists($fullDeletePath)) {
|
||||||
|
// Versuchen zu löschen. Wenn es fehlschlägt, Fehlermeldung erzwingen
|
||||||
|
if (!@unlink($fullDeletePath)) {
|
||||||
|
$error = error_get_last();
|
||||||
|
die("Datei existiert, aber PHP darf sie nicht löschen! Grund: " . $error['message']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Wenn PHP die Datei an diesem Pfad nicht findet, brechen wir zum Debuggen ab
|
||||||
|
// die("PHP findet die Datei nicht unter dem Pfad: " . $fullDeletePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// ----------------------- NEU hinzugefügte Base64-Bilder: --------------------------
|
||||||
|
if (is_array($blocks)) {
|
||||||
|
foreach ($blocks as &$block) {
|
||||||
|
// Prüfen, ob der Block ein Bild ist und ein NEUES Bild (Base64-Format) enthält
|
||||||
|
if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && is_string($block['value'])) {
|
||||||
|
|
||||||
|
if (str_starts_with($block['value'], 'data:image/')) {
|
||||||
|
$parts = explode(',', $block['value']);
|
||||||
|
if (count($parts) >= 2) {
|
||||||
|
$metadata = $parts[0];
|
||||||
|
$base64Data = $parts[1];
|
||||||
|
|
||||||
|
preg_match('/data:image\/(?<extension>.*?);/', $metadata, $matches);
|
||||||
|
$extension = $matches['extension'] ?? 'jpg';
|
||||||
|
if ($extension === 'jpeg') { $extension = 'jpg'; }
|
||||||
|
|
||||||
|
$fileName = 'img_' . uniqid() . '.' . $extension;
|
||||||
|
$filePath = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (file_put_contents($filePath, base64_decode($base64Data)) !== false) {
|
||||||
|
$block['value'] = 'uploads/' . $fileName;
|
||||||
|
} else {
|
||||||
|
$_SESSION["message"] = "image_upload_error";
|
||||||
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiertes Array wieder in JSON konvertieren
|
||||||
|
$finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||||
try {
|
try {
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
$article = $articleManager->getArticle($id);
|
$article = $articleManager->getArticle($id);
|
||||||
$article->setTitle($title);
|
$article->setTitle($title);
|
||||||
$article->setContent($content);
|
$article->setContent($finalContent);
|
||||||
$article->setCategory($category);
|
$article->setCategory($category);
|
||||||
$article->setTags($cleanedTags);
|
$article->setTags($cleanedTags);
|
||||||
$articleManager->updateArticle($id ,$article, $author);
|
$articleManager->updateArticle($id ,$article, $author);
|
||||||
} catch (Exception $e){
|
|
||||||
|
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e){
|
||||||
$_SESSION["message"] = $e->getMessage();
|
$_SESSION["message"] = $e->getMessage();
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
exit();
|
exit();
|
||||||
|
|||||||
+49
-10
@@ -7,13 +7,14 @@
|
|||||||
*/
|
*/
|
||||||
class Article
|
class Article
|
||||||
{
|
{
|
||||||
public $id;
|
private $id;
|
||||||
public $title;
|
private $title;
|
||||||
public $content;
|
private $content;
|
||||||
public $author;
|
private $author;
|
||||||
public $creationDate;
|
private $creationDate;
|
||||||
public $category;
|
private $category;
|
||||||
public $tags;
|
private $tags;
|
||||||
|
private $likes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Konstruktor
|
* Konstruktor
|
||||||
@@ -26,7 +27,7 @@ class Article
|
|||||||
* @param $tags string optionale Schlagworte für eine bessere Suche
|
* @param $tags string optionale Schlagworte für eine bessere Suche
|
||||||
* @param $creationDate string Datum der Beitragserstellung
|
* @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->id = $id;
|
||||||
$this->title = $title;
|
$this->title = $title;
|
||||||
@@ -35,6 +36,7 @@ class Article
|
|||||||
$this->creationDate = $creationDate;
|
$this->creationDate = $creationDate;
|
||||||
$this->category = $category;
|
$this->category = $category;
|
||||||
$this->tags = $tags;
|
$this->tags = $tags;
|
||||||
|
$this->likes = $likes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +69,7 @@ class Article
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Content eines Beitrags zurück.
|
* Gibt den Content eines Beitrags zurück.
|
||||||
* TODO: Content muss noch definiert werden.
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getContent(): string
|
public function getContent(): string
|
||||||
@@ -77,7 +79,7 @@ class Article
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Content eines Beitrags.
|
* Setzt den Content eines Beitrags.
|
||||||
* TODO: Content muss noch definiert werden.
|
*
|
||||||
* @param $content
|
* @param $content
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -95,6 +97,16 @@ class Article
|
|||||||
return $this->author;
|
return $this->author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt einen neuen Autor eines Beitrages.
|
||||||
|
* @param string $author
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setAuthor(string $author)
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt das Veröffentlichungsdatum des Beitrags zurück.
|
* Gibt das Veröffentlichungsdatum des Beitrags zurück.
|
||||||
* @return string
|
* @return string
|
||||||
@@ -141,7 +153,34 @@ class Article
|
|||||||
$this->tags = $tags;
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -4,46 +4,23 @@ require_once 'DatabaseArticleManager.php';
|
|||||||
require_once 'Article.php';
|
require_once 'Article.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Klasse beinhaltet alle Methoden für die Operation mit den Artikel-Daten.
|
* Die Klasse beinhaltet alle Methoden für die Operation mit den Beitrags-Daten.
|
||||||
*
|
*
|
||||||
* @author Niklas Ortmann
|
* @author Niklas Ortmann
|
||||||
*/
|
*/
|
||||||
class ArticleManager
|
class ArticleManager
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Diese Methode erstellt, falls noch keine existiert, eine Instanz einer ArticleManager-Implementierung und
|
||||||
|
* erstellt Dummy-Beiträge.
|
||||||
|
*
|
||||||
|
* @throws InternalServerErrorException
|
||||||
|
*/
|
||||||
public static function getInstance()
|
public static function getInstance()
|
||||||
{
|
{
|
||||||
$articleManager = DatabaseArticleManager::getInstance(); // Hier kann zwischen dem lokalen und datenbankbasiertem ArticleManager gewechselt werden.
|
|
||||||
|
|
||||||
// Erstellen von Dummy-Beiträgen:
|
return DatabaseArticleManager::getInstance(); // Hier kann zwischen dem lokalen und datenbankbasiertem ArticleManager gewechselt werden.
|
||||||
if($articleManager->getArticle(1) == null ){
|
|
||||||
$articleManager->addArticle(
|
|
||||||
"Satz des Pythagoras",
|
|
||||||
"Der Satz des Pythagoras wurde von dem griechischen Philosophen Pythagoras von Samos formuliert und im dritten Jahrhundert vor Christus veröffentlicht. In der beigefügten Abbildung sehen wir ein rechtwinkliges Dreieck...",
|
|
||||||
"mustermann@web.de",
|
|
||||||
"mathe",
|
|
||||||
"Dreiecke, Dreiecksseiten berechnen"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if($articleManager->getArticle(2) == null ){
|
|
||||||
$articleManager->addArticle(
|
|
||||||
"Tunneleffekt",
|
|
||||||
"Der Tunneleffekt ist ein quantenmechanisches Phänomen, bei dem Teilchen...",
|
|
||||||
"mustermann@web.de",
|
|
||||||
"physik",
|
|
||||||
"Quantenphysik, Energie"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if($articleManager->getArticle(3) == null ){
|
|
||||||
$articleManager->addArticle(
|
|
||||||
"Datenschutz vs Datensicherheit",
|
|
||||||
"Datenschutz ist in unserer digital vernetzten Welt allgegenwärtig...",
|
|
||||||
"mustermann@web.de",
|
|
||||||
"informatik",
|
|
||||||
"Daten, DSGVO"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $articleManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,7 @@ interface ArticleManagerDAO
|
|||||||
* (Unabhängig von Groß-und Kleinschreibung)
|
* (Unabhängig von Groß-und Kleinschreibung)
|
||||||
* @param string $keyword Der eingegebene Suchbegriff.
|
* @param string $keyword Der eingegebene Suchbegriff.
|
||||||
* @return array Ein Array von Artikeln ,die dem Suchkriterium entsprechen. Wenn nichts gefunden wird, ein leeres Array.
|
* @return array Ein Array von Artikeln ,die dem Suchkriterium entsprechen. Wenn nichts gefunden wird, ein leeres Array.
|
||||||
|
* @throws InternalServerErrorException
|
||||||
*/
|
*/
|
||||||
public function search(string $keyword): array;
|
public function search(string $keyword): array;
|
||||||
|
|
||||||
@@ -91,6 +92,18 @@ interface ArticleManagerDAO
|
|||||||
*/
|
*/
|
||||||
public function getArticlesByCategory($category);
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repräsentiert einen Kommentar unter einem Beitrag.
|
||||||
|
*
|
||||||
|
* Ein Kommentar kann entweder ein Hauptkommentar sein
|
||||||
|
* oder eine Antwort auf einen anderen Kommentar.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
private int $id;
|
||||||
|
private int $articleId;
|
||||||
|
private ?int $parentCommentId;
|
||||||
|
private string $author;
|
||||||
|
private string $content;
|
||||||
|
private string $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen Kommentar.
|
||||||
|
*
|
||||||
|
* @param int $id Eindeutige ID des Kommentars
|
||||||
|
* @param int $articleId ID des zugehörigen Beitrags
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
* @param string $author Autor des Kommentars
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param string $created Erstellungsdatum des Kommentars
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
int $id,
|
||||||
|
int $articleId,
|
||||||
|
?int $parentCommentId,
|
||||||
|
string $author,
|
||||||
|
string $content,
|
||||||
|
string $created
|
||||||
|
) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->articleId = $articleId;
|
||||||
|
$this->parentCommentId = $parentCommentId;
|
||||||
|
$this->author = $author;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->created = $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return int Kommentar-ID
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des zugehörigen Beitrags zurück.
|
||||||
|
*
|
||||||
|
* @return int Beitrags-ID
|
||||||
|
*/
|
||||||
|
public function getArticleId(): int
|
||||||
|
{
|
||||||
|
return $this->articleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die ID des Eltern-Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return int|null ID des Eltern-Kommentars oder null
|
||||||
|
*/
|
||||||
|
public function getParentCommentId(): ?int
|
||||||
|
{
|
||||||
|
return $this->parentCommentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt zurück, ob der Kommentar eine Antwort ist.
|
||||||
|
*
|
||||||
|
* @return bool true wenn der Kommentar eine Antwort ist, sonst false
|
||||||
|
*/
|
||||||
|
public function isReply(): bool
|
||||||
|
{
|
||||||
|
return $this->parentCommentId !== null && $this->parentCommentId !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Autor des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string Autor
|
||||||
|
*/
|
||||||
|
public function getAuthor(): string
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Inhalt des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string Kommentarinhalt
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt das Erstellungsdatum des Kommentars zurück.
|
||||||
|
*
|
||||||
|
* @return string Erstellungsdatum
|
||||||
|
*/
|
||||||
|
public function getCreated(): string
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "DatabaseCommentManager.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zentrale Zugriffsschicht für Kommentare.
|
||||||
|
*
|
||||||
|
* Die Anwendung arbeitet ausschließlich
|
||||||
|
* mit dem CommentManager und kennt die
|
||||||
|
* konkrete Speicherimplementierung nicht.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class CommentManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gibt die aktive Kommentarverwaltung zurück.
|
||||||
|
*
|
||||||
|
* @return CommentManagerDAO
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
return DatabaseCommentManager::getInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "Comment.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schnittstelle für die Verwaltung von Kommentaren.
|
||||||
|
*
|
||||||
|
* Definiert die grundlegenden Methoden zum
|
||||||
|
* Speichern und Laden von Kommentaren.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
interface CommentManagerDAO
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Speichert einen neuen Kommentar zu einem Beitrag.
|
||||||
|
*
|
||||||
|
* Optional kann eine parentCommentId übergeben werden,
|
||||||
|
* wenn der Kommentar eine Antwort auf einen anderen Kommentar ist.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
* @param string $author Autor des Kommentars
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
*
|
||||||
|
* @return int ID des neu gespeicherten Kommentars
|
||||||
|
*/
|
||||||
|
public function addComment(
|
||||||
|
$articleId,
|
||||||
|
$author,
|
||||||
|
$content,
|
||||||
|
$parentCommentId = null
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Kommentare eines Beitrags zurück.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
*
|
||||||
|
* @return Comment[] Liste der Kommentare
|
||||||
|
*/
|
||||||
|
public function getCommentsByArticle(
|
||||||
|
$articleId
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Kommentare eines Autors zurück.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
*
|
||||||
|
* @return Comment[] Liste der Kommentare
|
||||||
|
*/
|
||||||
|
public function getCommentsByAuthor($author);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
require_once 'ArticleManagerDAO.php';
|
require_once 'ArticleManagerDAO.php';
|
||||||
require_once 'Article.php';
|
require_once 'Article.php';
|
||||||
/**
|
/**
|
||||||
@@ -9,50 +10,35 @@ require_once 'Article.php';
|
|||||||
class DatabaseArticleManager implements ArticleManagerDAO {
|
class DatabaseArticleManager implements ArticleManagerDAO {
|
||||||
|
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Konstruktor
|
* Konstruktor
|
||||||
*
|
*
|
||||||
* Erstellt die Datenbankverbindung und die Tabelle articles.
|
* Erstellt die Datenbankverbindung und die Tabelle articles.
|
||||||
* @throws InternalServerErrorException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
if (!file_exists(__DIR__ . '/../../db/articles.db')) {
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
try {
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
|
||||||
$db = $this->getConnection();
|
|
||||||
|
|
||||||
$db->exec("
|
|
||||||
CREATE TABLE articles (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
title TEXT,
|
|
||||||
content TEXT,
|
|
||||||
author TEXT,
|
|
||||||
category TEXT,
|
|
||||||
tags TEXT,
|
|
||||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);");
|
|
||||||
unset($db);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
throw new InternalServerErrorException($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut die Verbindung zur Datenbank auf.
|
* Baut die Verbindung zur Datenbank auf.
|
||||||
* @throws InternalServerErrorException
|
*
|
||||||
|
* @return PDO Datenbankverbindung
|
||||||
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
private function getConnection()
|
private function getConnection()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$user = 'root';
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
$pw = null;
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
$dsn = 'sqlite:' . __DIR__ . '/../../db/articles.db';
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
return new PDO($dsn, $user, $pw);
|
return $db;
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
throw new InternalServerErrorException($e->getMessage());
|
throw new RuntimeException("internal_error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +175,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$row = $command->fetch(PDO::FETCH_ASSOC);
|
$row = $command->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($row) {
|
if ($row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
return new Article(
|
return new Article(
|
||||||
intval($row['id']),
|
intval($row['id']),
|
||||||
$row['title'],
|
$row['title'],
|
||||||
@@ -196,7 +184,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$row['author'],
|
$row['author'],
|
||||||
$row['category'],
|
$row['category'],
|
||||||
$row['tags'],
|
$row['tags'],
|
||||||
$row['created']
|
$row['created'],
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +243,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$filteredArticles = [];
|
$filteredArticles = [];
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
$filteredArticles[] = new Article(
|
$filteredArticles[] = new Article(
|
||||||
intval($row['id']),
|
intval($row['id']),
|
||||||
$row['title'],
|
$row['title'],
|
||||||
@@ -261,7 +252,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$row['author'],
|
$row['author'],
|
||||||
$row['category'],
|
$row['category'],
|
||||||
$row['tags'],
|
$row['tags'],
|
||||||
$row['created']
|
$row['created'],
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +279,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$filteredArticles = [];
|
$filteredArticles = [];
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
$filteredArticles[] = new Article(
|
$filteredArticles[] = new Article(
|
||||||
intval($row['id']),
|
intval($row['id']),
|
||||||
$row['title'],
|
$row['title'],
|
||||||
@@ -294,7 +288,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
$row['author'],
|
$row['author'],
|
||||||
$row['category'],
|
$row['category'],
|
||||||
$row['tags'],
|
$row['tags'],
|
||||||
$row['created']
|
$row['created'],
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,8 +301,126 @@ class DatabaseArticleManager implements ArticleManagerDAO {
|
|||||||
|
|
||||||
public function search(string $keyword): array
|
public function search(string $keyword): array
|
||||||
{
|
{
|
||||||
// TODO: implement search()
|
$cleankeyword = trim($keyword);
|
||||||
|
|
||||||
|
// Leeren Suchbegriff sofort abfangen
|
||||||
|
if ($cleankeyword === '') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "SELECT id, title, content, author, category, tags, created
|
||||||
|
FROM articles
|
||||||
|
WHERE title LIKE :keyword
|
||||||
|
OR content LIKE :keyword
|
||||||
|
OR tags LIKE :keyword";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
if (!$command) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wildcards für die Suche hinzufügen
|
||||||
|
$searchParam = '%' . $cleankeyword . '%';
|
||||||
|
|
||||||
|
$success = $command->execute([
|
||||||
|
":keyword" => $searchParam
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$success) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$filteredArticles = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$likes = $this->getLikesForArticle(intval($row['id']));
|
||||||
|
|
||||||
|
$filteredArticles[] = new Article(
|
||||||
|
intval($row['id']),
|
||||||
|
$row['title'] ?? '',
|
||||||
|
$row['content'] ?? '',
|
||||||
|
$row['author'] ?? '',
|
||||||
|
$row['category'] ?? '',
|
||||||
|
$row['tags'] ?? '',
|
||||||
|
$row['created'] ?? '',
|
||||||
|
$likes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filteredArticles;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new InternalServerErrorException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
|
require_once "CommentManagerDAO.php";
|
||||||
|
require_once "Comment.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verwaltet die Speicherung und das Laden von Kommentaren
|
||||||
|
* über eine SQLite-Datenbank.
|
||||||
|
*
|
||||||
|
* @author Caroline Schulte
|
||||||
|
*/
|
||||||
|
class DatabaseCommentManager implements CommentManagerDAO
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die Kommentartabelle, falls diese noch nicht existiert.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die Verbindung zur SQLite-Datenbank auf.
|
||||||
|
*
|
||||||
|
* @return PDO Datenbankverbindung
|
||||||
|
*/
|
||||||
|
private function getConnection()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
return $db;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Singleton-Instanz zurück.
|
||||||
|
*
|
||||||
|
* @return DatabaseCommentManager
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new DatabaseCommentManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert einen neuen Kommentar oder eine Antwort.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
* @param string $author Autor des Kommentars
|
||||||
|
* @param string $content Inhalt des Kommentars
|
||||||
|
* @param int|null $parentCommentId ID des Eltern-Kommentars oder null
|
||||||
|
*
|
||||||
|
* @return int ID des neu gespeicherten Kommentars
|
||||||
|
*/
|
||||||
|
public function addComment(
|
||||||
|
$articleId,
|
||||||
|
$author,
|
||||||
|
$content,
|
||||||
|
$parentCommentId = null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
if ($parentCommentId === "" || $parentCommentId === 0 || $parentCommentId === "0") {
|
||||||
|
$parentCommentId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
INSERT INTO comments (
|
||||||
|
article_id,
|
||||||
|
parent_comment_id,
|
||||||
|
author,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
:articleId,
|
||||||
|
:parentCommentId,
|
||||||
|
:author,
|
||||||
|
:content
|
||||||
|
)
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
|
||||||
|
$command->execute([
|
||||||
|
":articleId" => $articleId,
|
||||||
|
":parentCommentId" => $parentCommentId,
|
||||||
|
":author" => $author,
|
||||||
|
":content" => $content
|
||||||
|
]);
|
||||||
|
|
||||||
|
return intval($db->lastInsertId());
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Kommentare eines Beitrags.
|
||||||
|
*
|
||||||
|
* @param int $articleId ID des Beitrags
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
public function getCommentsByArticle($articleId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
article_id,
|
||||||
|
CASE
|
||||||
|
WHEN parent_comment_id IS NULL THEN NULL
|
||||||
|
WHEN parent_comment_id = '' THEN NULL
|
||||||
|
WHEN parent_comment_id = 0 THEN NULL
|
||||||
|
ELSE parent_comment_id
|
||||||
|
END AS parent_comment_id,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
|
created
|
||||||
|
FROM comments
|
||||||
|
WHERE article_id = :articleId
|
||||||
|
ORDER BY created ASC
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":articleId" => $articleId]);
|
||||||
|
|
||||||
|
return $this->mapRowsToComments($command);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Kommentare eines Autors.
|
||||||
|
*
|
||||||
|
* @param string $author E-Mail-Adresse des Autors
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
public function getCommentsByAuthor($author)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = $this->getConnection();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
article_id,
|
||||||
|
CASE
|
||||||
|
WHEN parent_comment_id IS NULL THEN NULL
|
||||||
|
WHEN parent_comment_id = '' THEN NULL
|
||||||
|
WHEN parent_comment_id = 0 THEN NULL
|
||||||
|
ELSE parent_comment_id
|
||||||
|
END AS parent_comment_id,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
|
created
|
||||||
|
FROM comments
|
||||||
|
WHERE author = :author
|
||||||
|
ORDER BY created DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$command = $db->prepare($sql);
|
||||||
|
$command->execute([":author" => $author]);
|
||||||
|
|
||||||
|
return $this->mapRowsToComments($command);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt Datenbankzeilen in Comment-Objekte um.
|
||||||
|
*
|
||||||
|
* @param PDOStatement $command Ausgeführtes Statement
|
||||||
|
*
|
||||||
|
* @return Comment[]
|
||||||
|
*/
|
||||||
|
private function mapRowsToComments($command)
|
||||||
|
{
|
||||||
|
$comments = [];
|
||||||
|
|
||||||
|
while ($row = $command->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$parentCommentId = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset($row["parent_comment_id"])
|
||||||
|
&& $row["parent_comment_id"] !== null
|
||||||
|
&& $row["parent_comment_id"] !== ""
|
||||||
|
&& intval($row["parent_comment_id"]) !== 0
|
||||||
|
) {
|
||||||
|
$parentCommentId = intval($row["parent_comment_id"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$comments[] = new Comment(
|
||||||
|
intval($row["id"]),
|
||||||
|
intval($row["article_id"]),
|
||||||
|
$parentCommentId,
|
||||||
|
$row["author"],
|
||||||
|
$row["content"],
|
||||||
|
$row["created"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $comments;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Diese Klasse legt einmalig beim allerersten Start in der exakt korrekten Reihenfolge die Tabellen an
|
||||||
|
*
|
||||||
|
* @author Niklas Ortmann
|
||||||
|
*/
|
||||||
|
class DatabaseInitializer {
|
||||||
|
public static function initialize($dbPath) {
|
||||||
|
if (!file_exists($dbPath)) {
|
||||||
|
try {
|
||||||
|
$db = new PDO('sqlite:' . $dbPath);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
email TEXT PRIMARY KEY,
|
||||||
|
vorname TEXT NOT NULL,
|
||||||
|
nachname TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS articles (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT,
|
||||||
|
content TEXT,
|
||||||
|
author TEXT,
|
||||||
|
category TEXT,
|
||||||
|
tags TEXT,
|
||||||
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (author) REFERENCES users(email) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS likes (
|
||||||
|
article_id INTEGER,
|
||||||
|
user_id TEXT,
|
||||||
|
PRIMARY KEY (article_id, user_id),
|
||||||
|
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(email) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS comments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
article_id INTEGER NOT NULL,
|
||||||
|
parent_comment_id INTEGER NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (author) REFERENCES users(email) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (parent_comment_id) REFERENCES comments(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
");
|
||||||
|
|
||||||
|
$initializer = new self();
|
||||||
|
$availableEmails = $initializer->seedDummyUsers($db);
|
||||||
|
$initializer->seedDummyArticles($db, $availableEmails);
|
||||||
|
|
||||||
|
unset($db);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("internal-error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die Dummy-Benutzer in der Datenbank.
|
||||||
|
*
|
||||||
|
* @param PDO $db Die aktive Datenbankverbindung
|
||||||
|
* @return array Liste der erfolgreich angelegten E-Mail-Adressen
|
||||||
|
*/
|
||||||
|
private function seedDummyUsers(PDO $db): array {
|
||||||
|
$dummyUsers = [
|
||||||
|
['email' => 'max.mustermann@web.de', 'vorname' => 'Max', 'nachname' => 'Mustermann'],
|
||||||
|
['email' => 'erika.mustermann@web.de', 'vorname' => 'Erika', 'nachname' => 'Mustermann'],
|
||||||
|
['email' => 'john.doe@gmail.com', 'vorname' => 'John', 'nachname' => 'Doe'],
|
||||||
|
['email' => 'jane.doe@gmail.com', 'vorname' => 'Jane', 'nachname' => 'Doe'],
|
||||||
|
['email' => 'anna.schmidt@gmx.de', 'vorname' => 'Anna', 'nachname' => 'Schmidt'],
|
||||||
|
['email' => 'thomas.mueller@gmx.de', 'vorname' => 'Thomas', 'nachname' => 'Müller'],
|
||||||
|
['email' => 'sabine.fischer@outlook.com', 'vorname' => 'Sabine', 'nachname' => 'Fischer'],
|
||||||
|
['email' => 'michael.weber@outlook.com', 'vorname' => 'Michael', 'nachname' => 'Weber'],
|
||||||
|
['email' => 'julia.wagner@t-online.de', 'vorname' => 'Julia', 'nachname' => 'Wagner'],
|
||||||
|
['email' => 'stefan.becker@t-online.de', 'vorname' => 'Stefan', 'nachname' => 'Becker']
|
||||||
|
];
|
||||||
|
|
||||||
|
$passwordHash = password_hash("test12345", PASSWORD_DEFAULT);
|
||||||
|
$userInsertStmt = $db->prepare("INSERT INTO users (email, vorname, nachname, password) VALUES (:email, :vorname, :nachname, :password);");
|
||||||
|
|
||||||
|
$availableEmails = [];
|
||||||
|
foreach ($dummyUsers as $user) {
|
||||||
|
$userInsertStmt->execute([
|
||||||
|
':email' => $user['email'],
|
||||||
|
':vorname' => $user['vorname'],
|
||||||
|
':nachname' => $user['nachname'],
|
||||||
|
':password' => $passwordHash
|
||||||
|
]);
|
||||||
|
$availableEmails[] = $user['email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $availableEmails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die 100 Fachbeiträge in der Datenbank.
|
||||||
|
*
|
||||||
|
* @param PDO $db Die aktive Datenbankverbindung
|
||||||
|
* @param array $availableEmails Pool für die Foreign-Key-Zuweisung des Autors
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function seedDummyArticles(PDO $db, array $availableEmails): void
|
||||||
|
{
|
||||||
|
$dummyArticles = [
|
||||||
|
// --- INFORMATIK & MATHE (1-20) ---
|
||||||
|
1 => ["Satz des Pythagoras", "Der Satz des Pythagoras beschreibt das Verhältnis der Seitenlängen in einem rechtwinkligen Dreieck.", "mathe", "Dreiecke, Geometrie"],
|
||||||
|
2 => ["Tunneleffekt", "Der Tunneleffekt beschreibt das quantenmechanische Phänomen, dass Teilchen Barrieren überwinden können.", "physik", "Quantenphysik, Energie"],
|
||||||
|
3 => ["Datenschutz vs Datensicherheit", "Datenschutz schützt Personen, während Datensicherheit Systeme vor unbefugten Zugriffen schützt.", "informatik", "Daten, DSGVO"],
|
||||||
|
4 => ["Einführung in Algorithmen", "Ein Algorithmus ist eine präzise Handlungsanweisung zur Lösung eines vordefinierten Problems.", "informatik", "Code, Logik"],
|
||||||
|
5 => ["Primzahlen im Detail", "Primzahlen sind natürliche Zahlen, die nur durch eins und sich selbst ohne Rest teilbar sind.", "mathe", "Zahlentheorie"],
|
||||||
|
6 => ["Lineare Algebra", "Vektoren und Matrizen bilden das Fundament für moderne Computergrafik und 3D-Engines.", "mathe", "Matrizen, Vektoren"],
|
||||||
|
7 => ["Objektorientierte Programmierung", "Die OOP nutzt Klassen und Objekte, um reale Strukturen im Quellcode abzubilden.", "informatik", "OOP, Klassen"],
|
||||||
|
8 => ["Grundlagen von HTML und CSS", "HTML strukturiert den Inhalt einer Webseite, während CSS für das optische Design zuständig ist.", "informatik", "Web, Frontend"],
|
||||||
|
9 => ["Die Relativitätstheorie", "Einsteins Theorie revolutionierte unser Verständnis von Raum, Zeit und Gravitation massiv.", "physik", "Einstein, Gravitation"],
|
||||||
|
10 => ["Datenbanken und SQL", "Strukturierte Abfragesprachen erlauben das effiziente Speichern und Abrufen großer Datenmengen.", "informatik", "SQL, Datenbank"],
|
||||||
|
11 => ["Wahrscheinlichkeitsrechnung", "Die Stochastik befasst sich mit der mathematischen Analyse von Zufallsexperimenten.", "mathe", "Zufall, Stochastik"],
|
||||||
|
12 => ["Quantencomputing", "Künftige Quantencomputer nutzen Qubits, um komplexe Berechnungen in Rekordzeit zu lösen.", "informatik", "Hardware, Zukunft"],
|
||||||
|
13 => ["Die Fibonacci-Folge", "Diese Zahlenreihe beschreibt mathematische Wachstumsmuster, die oft in der Natur vorkommen.", "mathe", "Zahlen, Natur"],
|
||||||
|
14 => ["Thermodynamik", "Die Hauptsätze der Thermodynamik regeln den Energieaustausch und die Entropie in Systemen.", "physik", "Energie, Wärme"],
|
||||||
|
15 => ["Cybersecurity Grundlagen", "Die Absicherung von Netzwerken erfordert Firewalls, Verschlüsselung und regelmäßige Audits.", "informatik", "Sicherheit, Hacker"],
|
||||||
|
16 => ["Kryptographie", "Asymmetrische Verschlüsselungsverfahren sichern heutzutage den gesamten Datenverkehr im Web.", "informatik", "Krypto, Security"],
|
||||||
|
17 => ["Analysis und Ableitungen", "Die Differentialrechnung untersucht die lokalen Änderungsraten von mathematischen Funktionen.", "mathe", "Analysis, Funktionen"],
|
||||||
|
18 => ["Schwarze Löcher", "Diese Regionen im Raum besitzen eine so starke Gravitation, dass selbst Licht nicht entkommt.", "physik", "Astronomie, Kosmos"],
|
||||||
|
19 => ["Git Versionsverwaltung", "Git erlaubt Entwicklern das parallele Arbeiten an Code-Projekten ohne Datenverlust.", "informatik", "Git, DevOps"],
|
||||||
|
20 => ["Künstliche Intelligenz", "Neuronale Netze versuchen das menschliche Gehirn für maschinelles Lernen nachzubilden.", "informatik", "KI, Software"],
|
||||||
|
|
||||||
|
// --- CHEMIE & BIOLOGIE (21-40) ---
|
||||||
|
21 => ["Das Periodensystem", "Die Elemente sind nach ihrer Ordnungszahl und chemischen Eigenschaften geordnet.", "chemie", "Elemente, Moleküle"],
|
||||||
|
22 => ["Photosynthese", "Pflanzen wandeln mithilfe von Sonnenlicht Kohlendioxid und Wasser in Glukose und Sauerstoff um.", "biologie", "Pflanzen, Energie"],
|
||||||
|
23 => ["Aufbau einer Zelle", "Die Zelle ist die kleinste lebende Einheit aller Organismen mit spezialisierten Organellen.", "biologie", "Zellen, Biologie"],
|
||||||
|
24 => ["Säuren und Basen", "Der pH-Wert misst die Konzentration von Wasserstoff-Ionen in einer wässrigen Lösung.", "chemie", "Labor, pH-Wert"],
|
||||||
|
25 => ["Die DNA-Struktur", "Die Doppelhelix enthält den genetischen Bauplan für die Entwicklung aller Lebewesen.", "biologie", "Genetik, Erbgut"],
|
||||||
|
26 => ["Chemische Bindungen", "Kovalente Bindungen entstehen durch das Teilen von Elektronenpaaren zwischen Atomen.", "chemie", "Atome, Bindung"],
|
||||||
|
27 => ["Das Immunsystem", "Weiße Blutkörperchen und Antikörper schützen den menschlichen Körper vor Krankheitserregern.", "biologie", "Gesundheit, Abwehr"],
|
||||||
|
28 => ["Katalysatoren", "Katalysatoren beschleunigen chemische Reaktionen, ohne dabei selbst verbraucht zu werden.", "chemie", "Reaktion, Chemie"],
|
||||||
|
29 => ["Ökosystem Wald", "Das Zusammenspiel von Flora, Fauna und Klima bildet ein hochsensibles ökologisches System.", "biologie", "Natur, Wald"],
|
||||||
|
30 => ["Die Mendelschen Regeln", "Diese Grundgesetze der Vererbung beschreiben, wie Merkmale an Nachkommen weitergegeben werden.", "biologie", "Genetik, Erbung"],
|
||||||
|
31 => ["Zustandsformen der Materie", "Fest, flüssig und gasförmig sind die klassischen Aggregatzustände von Stoffen.", "chemie", "Physik, Materie"],
|
||||||
|
32 => ["Evolutionstheorie", "Charles Darwin begründete die Theorie der natürlichen Auslese und Anpassung von Arten.", "biologie", "Darwin, Evolution"],
|
||||||
|
33 => ["Organische Chemie", "Die Chemie der Kohlenstoffverbindungen bildet die Basis für alles bekannte Leben.", "chemie", "Kohlenstoff, Chemie"],
|
||||||
|
34 => ["Das menschliche Gehirn", "Milliarden von Neuronen kommunizieren über Synapsen, um Reize und Gedanken zu verarbeiten.", "biologie", "Neurologie, Nerven"],
|
||||||
|
35 => ["Der Wasserkreislauf", "Verdunstung, Kondensation und Niederschlag halten das Wasser auf der Erde in Bewegung.", "geographie", "Wasser, Klima"],
|
||||||
|
36 => ["Aggregatzustände von Wasser", "Wasser zeigt ungewöhnliche Eigenschaften wie die Dichteanomalie beim Gefrieren.", "chemie", "Wasser, Eis"],
|
||||||
|
37 => ["Blutkreislauf des Menschen", "Das Herz pumpt sauerstoffreiches Blut durch Arterien in alle Organe des Körpers.", "biologie", "Herz, Medizin"],
|
||||||
|
38 => ["Das Ohmsche Gesetz", "Es beschreibt den direkten Zusammenhang zwischen Spannung, Stromstärke und Widerstand.", "physik", "Strom, Elektronik"],
|
||||||
|
39 => ["Plattentektonik", "Die Bewegung der Kontinentalplatten führt zu Erdbeben, Vulkanismus und Gebirgsbildung.", "geographie", "Erde, Geologie"],
|
||||||
|
40 => ["Proteine und Enzyme", "Enzyme wirken als Biokatalysatoren und steuern fast alle Stoffwechselprozesse.", "biologie", "Biochemie, Enzyme"],
|
||||||
|
|
||||||
|
// --- GESCHICHTE & WIRTSCHAFT (41-60) ---
|
||||||
|
41 => ["Das Römische Reich", "Vom Stadtstaat zum Weltreich prägte Rom die Rechts- und Kulturgeschichte Europas.", "geschichte", "Antike, Rom"],
|
||||||
|
42 => ["Die Französische Revolution", "Freiheit, Gleichheit, Brüderlichkeit beendeten 1789 die absolute Monarchie in Frankreich.", "geschichte", "Europa, Politik"],
|
||||||
|
43 => ["Inflation erklärt", "Inflation bezeichnet die kontinuierliche Geldentwertung und den Kaufkraftverlust.", "wirtschaft", "Geld, Finanzen"],
|
||||||
|
44 => ["Die Industrielle Revolution", "Die Erfindung der Dampfmaschine veränderte die Produktion und die Gesellschaft tiefgreifend.", "geschichte", "Industrie, Arbeit"],
|
||||||
|
45 => ["Angebot und Nachfrage", "Dieses fundamentale Marktgesetz bestimmt den Preis von Gütern in einer freien Wirtschaft.", "wirtschaft", "Markt, Preise"],
|
||||||
|
46 => ["Der Buchdruck", "Johannes Gutenbergs Erfindung revolutionierte die Verbreitung von Wissen im Mittelalter.", "geschichte", "Medien, Wissen"],
|
||||||
|
47 => ["Die Entdeckung Amerikas", "Kolumbus' Seereise im Jahr 1492 leitete das Zeitalter der Kolonialisierung ein.", "geschichte", "Entdeckung, Seefahrt"],
|
||||||
|
48 => ["Kryptowährungen", "Bitcoin nutzt Blockchain-Technologie, um dezentralen digitalen Werttransfer zu erlauben.", "wirtschaft", "Blockchain, Finanzen"],
|
||||||
|
49 => ["Das antike Griechenland", "Die Wiege der Demokratie und Philosophie brachte Denker wie Sokrates und Platon hervor.", "geschichte", "Antike, Philosophie"],
|
||||||
|
50 => ["Globalisierung", "Die weltweite Verflechtung in Wirtschaft, Kultur und Politik bringt Chancen und Risiken.", "wirtschaft", "Weltwirtschaft, Handel"],
|
||||||
|
51 => ["Der Dreißigjährige Krieg", "Ein religiöser und politischer Konflikt verwüstete zwischen 1618 und 1648 Mitteleuropa.", "geschichte", "Krieg, Europa"],
|
||||||
|
52 => ["Die Weimarer Republik", "Die erste deutsche Demokratie scheiterte an wirtschaftlichen und politischen Krisen.", "geschichte", "Deutschland, Weimar"],
|
||||||
|
53 => ["Aktien und Börse", "Unternehmen beschaffen sich Kapital durch die Ausgabe von Anteilen an Investoren.", "wirtschaft", "Aktien, Investieren"],
|
||||||
|
54 => ["Das alte Ägypten", "Pharaonen, Pyramiden und Hieroglyphen zeugen von einer hochentwickelten Hochkultur am Nil.", "geschichte", "Ägypten, Antike"],
|
||||||
|
55 => ["Der Kalte Krieg", "Das Wettrüsten zwischen USA und UdSSR prägte die globale Politik der Nachkriegszeit.", "geschichte", "Ost-West, Politik"],
|
||||||
|
56 => ["Zentralbanken und Leitzins", "Durch Zinsänderungen steuern Notenbanken die Geldmenge und bekämpfen Inflation.", "wirtschaft", "Zinsen, Geldpolitik"],
|
||||||
|
57 => ["Die Seidenstraße", "Das historische Netzwerk von Handelsrouten verband über Jahrhunderte Asien und Europa.", "geschichte", "Handel, Asien"],
|
||||||
|
58 => ["Das Mittelalter", "Ritter, Burgen und das Feudalsystem prägten diese tausendjährige Epoche Europas.", "geschichte", "Mittelalter, Feudalismus"],
|
||||||
|
59 => ["Planwirtschaft vs Marktwirtschaft", "Zentrale staatliche Steuerung steht dem freien Spiel der Marktkräfte gegenüber.", "wirtschaft", "Systeme, Wirtschaft"],
|
||||||
|
60 => ["Die Berliner Mauer", "Ihr Bau 1961 zementierte die Teilung Deutschlands, ihr Fall 1989 beendete sie.", "geschichte", "DDR, Wiedervereinigung"],
|
||||||
|
|
||||||
|
// --- ANWENDUNGEN & WEITERE THEMEN (61-100) ---
|
||||||
|
61 => ["Cloud Computing", "Das Auslagern von Rechenleistung in das Internet spart lokale IT-Infrastruktur ein.", "informatik", "Cloud, Web"],
|
||||||
|
62 => ["Responsive Webdesign", "Moderne Webseiten passen ihr Layout dynamisch an Smartphones und Desktops an.", "informatik", "Design, CSS"],
|
||||||
|
63 => ["Der Treibhauseffekt", "Gase in der Atmosphäre verhindern das Entweichen von Wärme ins Weltall.", "physik", "Klima, Umwelt"],
|
||||||
|
64 => ["Mechanik und Kräfte", "Die Newtonschen Axiome beschreiben, wie Kräfte auf Körper wirken und sie bewegen.", "physik", "Newton, Kraft"],
|
||||||
|
65 => ["Die Mendelschen Gesetze", "Die Vererbung von Genen folgt klaren statistischen Wahrscheinlichkeiten.", "biologie", "Genetik, Erbsen"],
|
||||||
|
66 => ["Lichtgeschwindigkeit", "Im Vakuum bewegt sich Licht mit knapp 300.000 Kilometern pro Sekunde.", "physik", "Licht, Relativität"],
|
||||||
|
67 => ["Die Funktion von APIs", "Programmierschnittstellen erlauben den Datenaustausch zwischen verschiedenen Systemen.", "informatik", "API, Schnittstelle"],
|
||||||
|
68 => ["Der Goldstandard", "Ein historischen Währungssystem, bei dem Geld durch echtes Gold gedeckt war.", "wirtschaft", "Gold, Währung"],
|
||||||
|
69 => ["Der Wiener Kongress", "1815 ordneten die europäischen Mächte die Landkarte nach den Napoleonischen Kriegen neu.", "geschichte", "Europa, Diplomatie"],
|
||||||
|
70 => ["Integrierte Schaltkreise", "Mikrochips enthalten Millionen Transistoren auf kleinstem Raum für Logikschaltungen.", "informatik", "Hardware, Chips"],
|
||||||
|
71 => ["Die Magellan-Expedition", "Die erste erfolgreiche Weltumsegelung bewies praktisch die Kugelgestalt der Erde.", "geschichte", "Seefahrt, Erde"],
|
||||||
|
72 => ["Das Internet der Dinge", "Alltagsgegenstände werden vernetzt, um smarte Automatisierungen zu ermöglichen.", "informatik", "IoT, SmartHome"],
|
||||||
|
73 => ["Halbwertszeit", "Die Zeitspanne, in der sich die Hälfte der instabilen Atome radioaktiv abbaut.", "physik", "Atomphysik, Strahlung"],
|
||||||
|
74 => ["Elektromagnetismus", "Die Verknüpfung von elektrischen Strömen und magnetischen Feldern treibt Motoren an.", "physik", "Strom, Magnet"],
|
||||||
|
75 => ["Das Ökosystem Meer", "Ozeane regulieren das Weltklima und bieten Lebensraum für unzählige Arten.", "biologie", "Meer, Ökologie"],
|
||||||
|
76 => ["Einführung in Docker", "Containerisierung isoliert Anwendungen samt Abhängigkeiten für stabilen Betrieb.", "informatik", "Docker, DevOps"],
|
||||||
|
77 => ["Das Römische Recht", "Viele moderne europäische Gesetzbücher basieren auf antiken römischen Rechtsprinzipien.", "geschichte", "Recht, Gesetz"],
|
||||||
|
78 => ["Verhaltensbiologie", "Untersuchung von angeborenen und erlernten Verhaltensweisen bei Mensch und Tier.", "biologie", "Verhalten, Tiere"],
|
||||||
|
79 => ["Verschlüsselung im Alltag", "HTTPS schützt Passwörter und Zahlungsdaten beim Surfen vor dem Mitlesen.", "informatik", "Web, HTTPS"],
|
||||||
|
80 => ["Die Magna Carta", "1215 schränkte dieses Dokument die absolute Macht des englischen Königs ein.", "geschichte", "England, Verfassung"],
|
||||||
|
81 => ["Marktversagen", "Wenn der freie Markt Ressourcen unvollständig verteilt, muss der Staat eingreifen.", "wirtschaft", "Markt, Staat"],
|
||||||
|
82 => ["Optische Linsen", "Konvexe und konkave Linsen brechen Licht für Brillen, Mikroskope und Kameras.", "physik", "Optik, Licht"],
|
||||||
|
83 => ["Die Entstehung der Erde", "Vor rund 4,5 Milliarden Jahren ballte sich kosmischer Staub zu unserem Planeten.", "geographie", "Erde, Kosmos"],
|
||||||
|
84 => ["Grundlagen von JavaScript", "Diese Skriptsprache macht statische Webseiten interaktiv und dynamisch nutzbar.", "informatik", "JS, Webentwicklung"],
|
||||||
|
85 => ["Die industrielle Landwirtschaft", "Moderne Techniken sichern Welternährung, belasten jedoch oft die Umwelt.", "biologie", "Landwirtschaft, Umwelt"],
|
||||||
|
86 => ["Das Schwarze Jahr 1929", "Der New Yorker Börsencrash löste die verheerende Weltwirtschaftskrise aus.", "geschichte", "Krise, Finanzen"],
|
||||||
|
87 => ["Die Evolution des Menschen", "Der Stammbaum des Homo Sapiens entwickelte sich über Millionen Jahre in Afrika.", "biologie", "Mensch, Evolution"],
|
||||||
|
88 => ["Einführung in Linux", "Das Open-Source-Betriebssystem bildet das Rückgrat moderner Server-Infrastrukturen.", "informatik", "Linux, OS"],
|
||||||
|
89 => ["Der Urknall", "Die Urknalltheorie beschreibt den Beginn des Universums aus einer Singularität.", "physik", "Astronomie, Urknall"],
|
||||||
|
90 => ["Das Periodensystem der Elemente", "Dmitri Mendelejew ordnete Elemente logisch nach ihren Atommassen.", "chemie", "Periodensystem, Chemie"],
|
||||||
|
91 => ["Die Hanse", "Ein mächtiger mittelalterlicher Bund von Kaufleuten dominierte den Nordseehandel.", "geschichte", "Handel, Mittelalter"],
|
||||||
|
92 => ["Wirtschaftswachstum", "Die Steigerung des Bruttoinlandsprodukts gilt oft als Indikator für Wohlstand.", "wirtschaft", "BIP, Finanzen"],
|
||||||
|
93 => ["Neuronale Netze", "Diese Strukturen lernen durch mathematische Gewichtung aus riesigen Datenmengen.", "informatik", "KI, Mathematik"],
|
||||||
|
94 => ["Der Erste Weltkrieg", "Der globale Konflikt von 1914 bis 1918 zerstörte das alte europäische Machtgefüge.", "geschichte", "Europa, Krieg"],
|
||||||
|
95 => ["Das Gehirn und Hormone", "Botenstoffe steuern Gefühle, Schlafzyklen und Reaktionen des Körpers.", "biologie", "Medizin, Hormone"],
|
||||||
|
96 => ["SQL Joins erklärt", "Joins verknüpfen Daten aus mehreren Tabellen über gemeinsame Schlüssel.", "informatik", "SQL, Datenbanken"],
|
||||||
|
97 => ["Wellen-Teilchen-Dualismus", "Quantenobjekte zeigen je nach Messaufbau Eigenschaften von Wellen oder Teilchen.", "physik", "Quanten, Licht"],
|
||||||
|
98 => ["Der Absolutismus", "Der Sonnenkönig Ludwig XIV. verkörperte die unbeschränkte Herrschaft des Monarchen.", "geschichte", "Frankreich, Monarchie"],
|
||||||
|
99 => ["Die Funktion von Routern", "Netzwerkgeräte leiten Datenpakete über IP-Adressen an den richtigen Empfänger.", "informatik", "Netzwerk, Internet"],
|
||||||
|
100 => ["Die Entstehung des Geldes", "Vom Tauschhandel über Naturalgeld bis hin zu modernen digitalen Fiat-Währungen.", "wirtschaft", "Geld, Geschichte"]
|
||||||
|
|
||||||
|
];
|
||||||
|
$articleInsertStmt = $db->prepare("
|
||||||
|
INSERT INTO articles (title, content, author, category, tags)
|
||||||
|
VALUES (:title, :content, :author, :category, :tags);");
|
||||||
|
|
||||||
|
foreach ($dummyArticles as $article) {
|
||||||
|
// Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails
|
||||||
|
$randomAuthor = $availableEmails[array_rand($availableEmails)];
|
||||||
|
|
||||||
|
$articleInsertStmt->execute([
|
||||||
|
':title' => $article[0],
|
||||||
|
':content' => $article[1],
|
||||||
|
':author' => $randomAuthor,
|
||||||
|
':category' => $article[2],
|
||||||
|
':tags' => $article[3]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once 'DatabaseInitializer.php';
|
||||||
require_once "UserManagerDAO.php";
|
require_once "UserManagerDAO.php";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,6 +11,7 @@ require_once "UserManagerDAO.php";
|
|||||||
class DatabaseUserManager implements UserManagerDAO {
|
class DatabaseUserManager implements UserManagerDAO {
|
||||||
|
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
private $dbPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Konstruktor.
|
* Konstruktor.
|
||||||
@@ -22,23 +23,8 @@ class DatabaseUserManager implements UserManagerDAO {
|
|||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
try {
|
$this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
|
||||||
$db = $this->getConnection();
|
DatabaseInitializer::initialize($this->dbPath);
|
||||||
|
|
||||||
$db->exec("
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
email TEXT PRIMARY KEY,
|
|
||||||
vorname TEXT NOT NULL,
|
|
||||||
nachname TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL
|
|
||||||
);
|
|
||||||
");
|
|
||||||
|
|
||||||
unset($db);
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
throw new RuntimeException("Benutzerdatenbank konnte nicht erstellt werden.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,15 +37,12 @@ class DatabaseUserManager implements UserManagerDAO {
|
|||||||
private function getConnection()
|
private function getConnection()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$dsn = 'sqlite:' . __DIR__ . '/../../db/users.db';
|
$db = new PDO('sqlite:' . $this->dbPath, null, null);
|
||||||
|
|
||||||
$db = new PDO($dsn, null, null);
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->exec('PRAGMA foreign_keys = ON;');
|
||||||
return $db;
|
return $db;
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
throw new RuntimeException("Verbindung zur Benutzerdatenbank fehlgeschlagen.");
|
throw new RuntimeException("internal_error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
"author" => $author,
|
"author" => $author,
|
||||||
"category" => $category,
|
"category" => $category,
|
||||||
"tags" => $tags,
|
"tags" => $tags,
|
||||||
"creationDate" => date("Y-m-d H:i:s")
|
"creationDate" => date("Y-m-d H:i:s"),
|
||||||
|
"likes" => []
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->saveArticle($articles);
|
$this->saveArticle($articles);
|
||||||
@@ -92,7 +93,8 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
"author" => $author,
|
"author" => $author,
|
||||||
"category" => $article->getCategory(),
|
"category" => $article->getCategory(),
|
||||||
"tags" => $article->getTags(),
|
"tags" => $article->getTags(),
|
||||||
"creationDate" => $article->getCreationDate()
|
"creationDate" => $article->getCreationDate(),
|
||||||
|
"likes" => $storedArticle['likes'] ?? []
|
||||||
];
|
];
|
||||||
$updated = true;
|
$updated = true;
|
||||||
break;
|
break;
|
||||||
@@ -142,7 +144,17 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
|
|
||||||
foreach ($articles as $article) {
|
foreach ($articles as $article) {
|
||||||
if (isset($article['id']) && $article['id'] == $id) {
|
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) {
|
foreach ($articles as $article) {
|
||||||
if (isset($article['author']) && $article['author'] == $author) {
|
if (isset($article['author']) && $article['author'] == $author) {
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
$filteredArticles[] = new Article(
|
$filteredArticles[] = new Article(
|
||||||
intval($article['id']),
|
intval($article['id']),
|
||||||
$article['title'],
|
$article['title'],
|
||||||
@@ -175,7 +188,8 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
$article['author'],
|
$article['author'],
|
||||||
$article['category'],
|
$article['category'],
|
||||||
$article['tags'],
|
$article['tags'],
|
||||||
$article['creationDate']
|
$article['creationDate'],
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,10 +211,13 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
foreach ($articles as $article) {
|
foreach ($articles as $article) {
|
||||||
$title = isset($article['title']) ? strtolower((string)$article['title']) : '';
|
$title = isset($article['title']) ? strtolower((string)$article['title']) : '';
|
||||||
$content = isset($article['content']) ? strtolower((string)$article['content']) : '';
|
$content = isset($article['content']) ? strtolower((string)$article['content']) : '';
|
||||||
|
$tags = isset($article['tags']) ? strtolower((string)$article['tags']) : '';
|
||||||
|
|
||||||
if (($cleanKeyword !== '' && strpos($title, $cleanKeyword) !== false) ||
|
if (($cleanKeyword !== '' && strpos($title, $cleanKeyword) !== false) ||
|
||||||
($cleanKeyword !== '' && strpos($content, $cleanKeyword) !== false)) {
|
($cleanKeyword !== '' && strpos($content, $cleanKeyword) !== false) ||
|
||||||
|
($cleanKeyword !== '' && strpos($tags, $cleanKeyword) !== false)) {
|
||||||
|
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
$filteredArticles[] = new Article(
|
$filteredArticles[] = new Article(
|
||||||
intval($article['id'] ?? 0),
|
intval($article['id'] ?? 0),
|
||||||
$article['title'] ?? '',
|
$article['title'] ?? '',
|
||||||
@@ -208,7 +225,8 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
$article['author'] ?? '',
|
$article['author'] ?? '',
|
||||||
$article['category'] ?? '',
|
$article['category'] ?? '',
|
||||||
$article['tags'] ?? '',
|
$article['tags'] ?? '',
|
||||||
$article['creationDate'] ?? ''
|
$article['creationDate'] ?? '',
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,6 +241,7 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
|
|
||||||
foreach ($articles as $article) {
|
foreach ($articles as $article) {
|
||||||
if (isset($article['category']) && $article['category'] == $category) {
|
if (isset($article['category']) && $article['category'] == $category) {
|
||||||
|
$likes = isset($article['likes']) && is_array($article['likes']) ? $article['likes'] : [];
|
||||||
$filteredArticles[] = new Article(
|
$filteredArticles[] = new Article(
|
||||||
intval($article['id']),
|
intval($article['id']),
|
||||||
$article['title'],
|
$article['title'],
|
||||||
@@ -230,12 +249,53 @@ class LocalArticleManager implements ArticleManagerDAO {
|
|||||||
$article['author'],
|
$article['author'],
|
||||||
$article['category'],
|
$article['category'],
|
||||||
$article['tags'],
|
$article['tags'],
|
||||||
$article['creationDate']
|
$article['creationDate'],
|
||||||
|
$likes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $filteredArticles;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -12,21 +12,8 @@ require_once "DatabaseUserManager.php";
|
|||||||
class UserManager {
|
class UserManager {
|
||||||
|
|
||||||
public static function getInstance() {
|
public static function getInstance() {
|
||||||
$userManager = DatabaseUserManager::getInstance();
|
|
||||||
|
|
||||||
/*
|
return DatabaseUserManager::getInstance();
|
||||||
* Dummy-User anlegen, falls er noch nicht existiert.
|
|
||||||
* Passwort: test123
|
|
||||||
*/
|
|
||||||
if ($userManager->findUser("mustermann@web.de") == null) {
|
|
||||||
$userManager->addUser(
|
|
||||||
"mustermann@web.de",
|
|
||||||
"Max",
|
|
||||||
"Mustermann",
|
|
||||||
password_hash("test12345", PASSWORD_DEFAULT)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $userManager;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* Prüft, ob der Autor auch der Eigentümer des Beitrags ist.
|
|
||||||
* @param $author
|
|
||||||
* @return true
|
|
||||||
* TODO: Implement this.
|
|
||||||
*/
|
|
||||||
function articleAuthorValidator($author)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob der Titel die folgenden Bedingungen erfüllt:
|
* Prüft, ob der Titel die folgenden Bedingungen erfüllt:
|
||||||
@@ -32,19 +22,73 @@ function articleTitleValidator($title)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob der Contenttext 10-7000 Zeichen enthält.
|
* Prüft, ob der Content valides JSON ist UND ob alle enthaltenen Blöcke
|
||||||
* @param $content
|
* die inhaltlichen Kriterien erfüllen:
|
||||||
|
* Textblöcke sind nicht leer
|
||||||
|
* Bilder sind in Bildblöcken vorhanden
|
||||||
|
*
|
||||||
|
* @param mixed $content Der zu prüfende JSON-String
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
function articleContentValidator($content)
|
function articleContentValidator($content)
|
||||||
{
|
{
|
||||||
$content = trim($content);
|
// 1. Grundlegende Typprüfung
|
||||||
$zeichenAnzahl = mb_strlen($content);
|
if (!is_string($content)) {
|
||||||
if ($zeichenAnzahl <= 7000 && $zeichenAnzahl >= 10) {
|
|
||||||
return true;
|
|
||||||
}else{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Formale JSON-Prüfung (Kompatibel mit PHP 8.2)
|
||||||
|
$blocks = json_decode($content, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Inhaltliche Validierung der einzelnen Blöcke
|
||||||
|
// Falls das JSON zwar valide, aber kein Array ist (z.B. nur ein String/Zahl)
|
||||||
|
if (!is_array($blocks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mindestens ein Block sollte vorhanden sein (optional, verhindert leere Beiträge)
|
||||||
|
if (empty($blocks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($blocks as $block) {
|
||||||
|
// Jeder Block muss die Keys 'type' und 'value' besitzen
|
||||||
|
if (!isset($block['type']) || !isset($block['value'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $block['type'];
|
||||||
|
$value = $block['value'];
|
||||||
|
|
||||||
|
if ($type === 'text') {
|
||||||
|
// Validierung für Text: Darf nach dem Trimmen nicht leer sein
|
||||||
|
if (trim($value) === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif ($type === 'image') {
|
||||||
|
// Validierung für Bild: Muss entweder mit uploads/ starten (Bestand)
|
||||||
|
// oder mit data:image/ beginnen (neues Base64-Bild aus dem Editor)
|
||||||
|
if (!is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isValidPath = str_starts_with($value, 'uploads/');
|
||||||
|
$isValidBase64 = str_starts_with($value, 'data:image/');
|
||||||
|
|
||||||
|
if (!$isValidPath && !$isValidBase64) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unbekannter Blocktyp wird zur Sicherheit abgewiesen
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn alle Prüfungen bestanden wurden
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
|||||||
|
# Verhindert das Auflisten aller Dateien im Ordner
|
||||||
|
Options -Indexes
|
||||||
|
|
||||||
|
# Schaltet die PHP-Ausführung in diesem Ordner komplett ab
|
||||||
|
<FilesMatch "\.(php|php[0-9]|phtml|pl|py|jsp|sh|cgi)$">
|
||||||
|
Order Deny,Allow
|
||||||
|
Deny from all
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# erlaubt nur den Zugriff auf folgende Dateien:
|
||||||
|
<FilesMatch "\.(?i:jpg|jpeg|png|gif|webp|ico)$">
|
||||||
|
Order Allow,Deny
|
||||||
|
Allow from all
|
||||||
|
</FilesMatch>
|
||||||
Reference in New Issue
Block a user