Merge branch 'dev' into 'main'
Potential Produkt Increment See merge request eb2342s/swe-b1-a!23
@@ -29,7 +29,7 @@ body {
|
||||
left: 0;
|
||||
top: 0;
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 50;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
@@ -100,7 +100,7 @@ body {
|
||||
.main-content {
|
||||
margin-left: 280px;
|
||||
width: calc(100% - 280px);
|
||||
padding-top: 5rem;
|
||||
padding-top: 3rem;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -287,15 +287,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
padding: 0.75rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Add floating animation for icons */
|
||||
@keyframes float {
|
||||
|
||||
@@ -28,7 +28,7 @@ body {
|
||||
left: 0;
|
||||
top: 0;
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 50;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
@@ -187,6 +187,16 @@ body {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Active nav link style */
|
||||
@@ -209,12 +219,3 @@ body {
|
||||
left: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
padding: 0.75rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Update search function with fallback animation
|
||||
function handleSearch() {
|
||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||
const topicCards = document.querySelectorAll('.topic-card');
|
||||
|
||||
topicCards.forEach(card => {
|
||||
const title = card.querySelector('.topic-title')?.textContent.toLowerCase() || '';
|
||||
const description = card.querySelector('.topic-description')?.textContent.toLowerCase() || '';
|
||||
const relatedTopics = Array.from(card.querySelectorAll('.related-topics li'))
|
||||
.map(li => li.textContent.toLowerCase())
|
||||
.join(' ');
|
||||
|
||||
const content = `${title} ${description} ${relatedTopics}`;
|
||||
|
||||
if (content.includes(searchTerm)) {
|
||||
card.style.display = 'block';
|
||||
if (window.gsap) {
|
||||
gsap.to(card, {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.3
|
||||
});
|
||||
} else {
|
||||
card.style.opacity = 1;
|
||||
card.style.transform = 'translateY(0)';
|
||||
}
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -27,7 +27,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 45;
|
||||
z-index: 30;
|
||||
transition: opacity 0.3s ease;
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
51
webseite/classes/Config.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Konfigurationsdaten
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* @return string Verzeichnis für generelle Konfigurationsdaten
|
||||
*/
|
||||
public static function getConfigDirectory(): string
|
||||
{
|
||||
return "config/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Verzeichnis für Fächer
|
||||
*/
|
||||
public static function getSubjectsDirectory(): string
|
||||
{
|
||||
return self::getConfigDirectory() . "subjects/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subjectId ID des Faches
|
||||
* @return string Verzeichnis des Faches
|
||||
*/
|
||||
public static function getSubjectDirectory(string $subjectId): string
|
||||
{
|
||||
return self::getSubjectsDirectory() . $subjectId . "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subjectId ID des Faches
|
||||
* @return string Verzeichnis der Themen des Faches
|
||||
*/
|
||||
public static function getTopicsDirectory(string $subjectId): string
|
||||
{
|
||||
return self::getSubjectDirectory($subjectId) . "topics/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subjectId ID des Faches
|
||||
* @param string $topicId ID des Themas
|
||||
* @return string Verzeichnis des Themas
|
||||
*/
|
||||
public static function getTopicDirectory(string $subjectId, string $topicId): string
|
||||
{
|
||||
return self::getTopicsDirectory($subjectId) . $topicId . "/";
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
require_once("Config.php");
|
||||
require_once("Util.php");
|
||||
require_once("TopicData.php");
|
||||
|
||||
/**
|
||||
* Stellt alle relevanten Daten für ein einzeles Fach bereit
|
||||
* Stellt alle relevanten Daten für ein einzelnes Fach bereit
|
||||
*
|
||||
*/
|
||||
class SubjectData
|
||||
@@ -24,7 +25,7 @@ class SubjectData
|
||||
public string $description;
|
||||
|
||||
/**
|
||||
* @var string Themenfarbe des Faches, entweder als hexcode oder CSS-Klasse, noch nicht spezifiziert
|
||||
* @var string Themenfarbe des Faches als hexcode
|
||||
*/
|
||||
public string $color;
|
||||
|
||||
@@ -39,6 +40,55 @@ class SubjectData
|
||||
*/
|
||||
public array $topics;
|
||||
|
||||
/**
|
||||
* Erstellt ein neues Fach. Es wird noch nichts gespeichert!
|
||||
* @param string $id Ein eindeutiger Bezeichner für das Fach, darf nur A-Z, a-Z, 0-9 sowie _ und - enthalten
|
||||
* @param string $displayName Der für User angezeigte Name des Faches, nur reiner Text
|
||||
* @param string $description Eine kurze Beschreibung des Faches, z.B. für den Text auf der Startseite, kann HTML enthalten
|
||||
* @param string $color Themenfarbe des Faches als hexcode
|
||||
* @param string $icon Icon des Faches als Font-Awesome CSS-Klasse
|
||||
* @param array $topics Alle Themen des Faches als TopicData Object
|
||||
* @return SubjectData|false Neues Fach oder false, wenn ein Fehler auftritt
|
||||
*/
|
||||
public static function createNew(string $id, string $displayName, string $description, string $color, string $icon, array $topics): SubjectData|false
|
||||
{
|
||||
$result = new SubjectData();
|
||||
|
||||
if(Util::containsIllegalCharacters($id)) {
|
||||
return false;
|
||||
}
|
||||
if(self::exists($id)) {
|
||||
return false;
|
||||
}
|
||||
$result->id = $id;
|
||||
|
||||
$result->displayName = $displayName;
|
||||
|
||||
$result->description = $description;
|
||||
|
||||
$result->color = $color;
|
||||
|
||||
$result->icon = $icon;
|
||||
|
||||
$result->topics = $topics;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Thema zu den angegebenen IDs existiert
|
||||
* @param string $subjectId ID des Faches
|
||||
* @return bool true, wenn es existiert, sonst false
|
||||
*/
|
||||
public static function exists(string $subjectId): bool
|
||||
{
|
||||
if(!is_dir(Config::getSubjectDirectory($subjectId))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Fächer als SubjectData Objekt zurück
|
||||
* @return array Alle Fächer als SubjectData
|
||||
@@ -47,8 +97,7 @@ class SubjectData
|
||||
{
|
||||
$result = array();
|
||||
|
||||
$subjectDirectory = "config/subjects";
|
||||
$subjectNames = scandir($subjectDirectory);
|
||||
$subjectNames = scandir(Config::getSubjectsDirectory());
|
||||
|
||||
usort($subjectNames, function ($a, $b) {
|
||||
return strcmp($a, $b);
|
||||
@@ -79,43 +128,171 @@ class SubjectData
|
||||
{
|
||||
$result = new SubjectData();
|
||||
|
||||
$subjectId = Util::removeIllegalCharacters($subjectId);
|
||||
if (Util::containsIllegalCharacters($subjectId)) {
|
||||
return null;
|
||||
}
|
||||
$result->id = $subjectId;
|
||||
|
||||
$subjectDirectory = "config/subjects/$subjectId";
|
||||
$filename = "$subjectDirectory/properties.json";
|
||||
$filename = Config::getSubjectDirectory($subjectId) . "properties.json";
|
||||
$data = Util::parseJsonFromFile($filename);
|
||||
if (!isset($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result->id = $subjectId;
|
||||
|
||||
if (isset($data->displayName)) {
|
||||
$result->displayName = $data->displayName;
|
||||
} else {
|
||||
if (!isset($data->displayName)) {
|
||||
return null;
|
||||
}
|
||||
$result->displayName = $data->displayName;
|
||||
|
||||
if (isset($data->description)) {
|
||||
$result->description = $data->description;
|
||||
} else {
|
||||
if (!isset($data->description)) {
|
||||
return null;
|
||||
}
|
||||
$result->description = $data->description;
|
||||
|
||||
if (isset($data->color)) {
|
||||
$result->color = $data->color;
|
||||
} else {
|
||||
if (!isset($data->color)) {
|
||||
return null;
|
||||
}
|
||||
$result->color = $data->color;
|
||||
|
||||
if (isset($data->icon)) {
|
||||
$result->icon = $data->icon;
|
||||
} else {
|
||||
if (!isset($data->icon)) {
|
||||
return null;
|
||||
}
|
||||
$result->icon = $data->icon;
|
||||
|
||||
$result->topics = TopicData::getAll($subjectId);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt alle Daten in Dateien
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
$data = array();
|
||||
$data["displayName"] = $this->displayName;
|
||||
$data["description"] = $this->description;
|
||||
$data["color"] = $this->color;
|
||||
$data["icon"] = $this->icon;
|
||||
|
||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
if (!$json) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subjectDirectory = Config::getSubjectDirectory($this->getId());
|
||||
if (!is_dir($subjectDirectory)) {
|
||||
mkdir($subjectDirectory, 0777, true);
|
||||
}
|
||||
|
||||
if (!Util::writeFileContent($subjectDirectory . "properties.json", $json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht das Fach inklusive aller zugehörigen Themen
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if (!Util::delete(Config::getSubjectDirectory($this->getId()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt ein neues Thema zum Fach hinzu
|
||||
* @param TopicData $topic Das neue Thema
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function addTopic(TopicData $topic): bool
|
||||
{
|
||||
if(isset($this->topics[$topic->getId()])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->topics[] = $topic;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt ein Thema vom Fach
|
||||
* @param TopicData $topic Das zu entfernende Thema
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function removeTopic(TopicData $topic): bool
|
||||
{
|
||||
if(!isset($this->topics[$topic->getId()])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->topics = array_diff($this->topics, [$topic]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(string $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function setDisplayName(string $displayName): void
|
||||
{
|
||||
$this->displayName = $displayName;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
public function setColor(string $color): void
|
||||
{
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function setIcon(string $icon): void
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
public function getTopics(): array
|
||||
{
|
||||
return $this->topics;
|
||||
}
|
||||
|
||||
public function setTopics(array $topics): void
|
||||
{
|
||||
$this->topics = $topics;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
<?php
|
||||
|
||||
use exception\SubjectDoesNotExistException;
|
||||
use exception\TopicAlreadyExistsException;
|
||||
|
||||
require_once("Config.php");
|
||||
require_once("Util.php");
|
||||
|
||||
class TopicData
|
||||
@@ -43,6 +48,67 @@ class TopicData
|
||||
*/
|
||||
public string $article;
|
||||
|
||||
/**
|
||||
* Erstellt ein neues Thema. Es wird noch nichts gespeichert!
|
||||
* @param string $id Innerhalb des zugehörigen Faches eindeutige ID, darf nur A-Z, a-z, 0-9 sowie - und _ enthalten
|
||||
* @param string $subjectId Die eindeutige ID des zugehörigen Faches, das Fach muss schon existieren
|
||||
* @param string $displayName Der für User angezeigt Name des Themas, darf nur reinen Text enthalten
|
||||
* @param string $icon Das Icon des Themas als Font-Awesome CSS-Klasse
|
||||
* @param string $description Eine kurze Beschreibung des Themas, z.B. für die Fachübersichtsseite, darf HTML enthalten
|
||||
* @param array $relatedTopics Die IDs aller verwandten Themen als String
|
||||
* @param string $article Der gesamte Erklärungstext zum Thema, enthält fertiges HTML und LATEX Formelsyntax für MathJax https://docs.mathjax.org/en/latest/basic/mathematics.html
|
||||
* @return TopicData|false Neues Thema oder false, wenn ein Fehler auftritt
|
||||
*/
|
||||
public static function createNew(string $id, string $subjectId, string $displayName, string $icon, string $description, array $relatedTopics, string $article): TopicData|false
|
||||
{
|
||||
$result = new TopicData();
|
||||
|
||||
if (Util::containsIllegalCharacters($subjectId)) {
|
||||
return false;
|
||||
}
|
||||
if (!SubjectData::exists($subjectId)) {
|
||||
return false;
|
||||
}
|
||||
$result->subjectId = $subjectId;
|
||||
|
||||
if (Util::containsIllegalCharacters($id)) {
|
||||
return false;
|
||||
}
|
||||
if (self::exists($subjectId, $id)) {
|
||||
return false;
|
||||
}
|
||||
$result->id = $id;
|
||||
|
||||
$result->displayName = $displayName;
|
||||
|
||||
$result->icon = $icon;
|
||||
|
||||
$result->description = $description;
|
||||
|
||||
$result->relatedTopics = $relatedTopics;
|
||||
|
||||
$result->files = array();
|
||||
|
||||
$result->article = $article;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Thema zu den angegebenen IDs existiert
|
||||
* @param string $subjectId ID des Faches
|
||||
* @param string $topicId ID des Themas
|
||||
* @return bool true, wenn es existiert, sonst false
|
||||
*/
|
||||
public static function exists(string $subjectId, string $topicId): bool
|
||||
{
|
||||
if (!is_dir(Config::getTopicDirectory($subjectId, $topicId))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Themen zu einem gegebenen Fach zurück
|
||||
* @param $subjectId string Die ID des Faches
|
||||
@@ -52,8 +118,7 @@ class TopicData
|
||||
{
|
||||
$result = array();
|
||||
|
||||
$topicDirectory = "config/subjects/$subjectId/topics";
|
||||
$topicNames = scandir($topicDirectory);
|
||||
$topicNames = scandir(Config::getTopicsDirectory($subjectId));
|
||||
|
||||
usort($topicNames, function ($a, $b) {
|
||||
return strcmp($a, $b);
|
||||
@@ -84,57 +149,319 @@ class TopicData
|
||||
{
|
||||
$result = new TopicData();
|
||||
|
||||
$subjectId = Util::removeIllegalCharacters($subjectId);
|
||||
$topicId = Util::removeIllegalCharacters($topicId);
|
||||
if (Util::containsIllegalCharacters($subjectId)) {
|
||||
return null;
|
||||
}
|
||||
if (Util::containsIllegalCharacters($topicId)) {
|
||||
return null;
|
||||
}
|
||||
$result->id = $topicId;
|
||||
$result->subjectId = $subjectId;
|
||||
|
||||
$topicsDirectory = "config/subjects/$subjectId/topics";
|
||||
$topicDataDirectory = "$topicsDirectory/$topicId";
|
||||
$data = Util::parseJsonFromFile("$topicDataDirectory/properties.json");
|
||||
$data = Util::parseJsonFromFile(Config::getTopicDirectory($subjectId, $topicId) . "properties.json");
|
||||
if (!isset($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result->id = $topicId;
|
||||
$result->subjectId = $subjectId;
|
||||
|
||||
if (isset($data->displayName)) {
|
||||
$result->displayName = $data->displayName;
|
||||
} else {
|
||||
if (!isset($data->displayName)) {
|
||||
return null;
|
||||
}
|
||||
$result->displayName = $data->displayName;
|
||||
|
||||
if (isset($data->icon)) {
|
||||
$result->icon = $data->icon;
|
||||
} else {
|
||||
if (!isset($data->icon)) {
|
||||
return null;
|
||||
}
|
||||
$result->icon = $data->icon;
|
||||
|
||||
if (isset($data->description)) {
|
||||
$result->description = $data->description;
|
||||
} else {
|
||||
if (!isset($data->description)) {
|
||||
return null;
|
||||
}
|
||||
$result->description = $data->description;
|
||||
|
||||
$relatedTopics = array();
|
||||
if (isset($data->relatedTopics)) {
|
||||
$result->relatedTopics = $data->relatedTopics;
|
||||
} else {
|
||||
$result->relatedTopics = array();
|
||||
}
|
||||
if (isset($data->files)) {
|
||||
$result->files = $data->files;
|
||||
} else {
|
||||
$result->files = array();
|
||||
$relatedTopics = $data->relatedTopics;
|
||||
}
|
||||
$result->relatedTopics = $relatedTopics;
|
||||
|
||||
$article = Util::readFileContent("$topicDataDirectory/article.html");
|
||||
$files = Util::getFilesFromDirectory(Config::getTopicDirectory($subjectId, $topicId) . "downloads/");
|
||||
$result->files = $files;
|
||||
|
||||
$article = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "article.html");
|
||||
if (!isset($article)) {
|
||||
$article = "Kein Erklärtext vorhanden";
|
||||
}
|
||||
$article = str_replace('$TOPICPATH', $topicDataDirectory, $article);
|
||||
$result->article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article);
|
||||
|
||||
$result->article = $article;
|
||||
$result->cleanupRelatedTopics();
|
||||
$result->cleanupFiles();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt alle Daten in Dateien
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
$this->cleanupRelatedTopics();
|
||||
$this->cleanupFiles();
|
||||
|
||||
$data = array();
|
||||
$data["displayName"] = $this->displayName;
|
||||
$data["icon"] = $this->icon;
|
||||
$data["description"] = $this->description;
|
||||
$data["relatedTopics"] = $this->relatedTopics;
|
||||
$data["files"] = $this->files;
|
||||
|
||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
if (!$json) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir(Config::getSubjectDirectory($this->getSubjectId()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$topicDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId());
|
||||
if (!is_dir($topicDirectory)) {
|
||||
mkdir($topicDirectory, 0777, true);
|
||||
}
|
||||
|
||||
if (!(Util::writeFileContent($topicDirectory . "properties.json", $json)
|
||||
&& Util::writeFileContent($topicDirectory . "article.html", $json))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt eine Datei als Download zum Thema hoch
|
||||
* @param string $name Dateiname von User, z.B. $_FILES['html-input-name']['name'][0]
|
||||
* @param string $tmp_name Temporärer Pfad zur hochgeladenen Datei, z.B. $_FILES['html-input-name']['tmp_name'][0]
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function addDownload(string $name, string $tmp_name): bool
|
||||
{
|
||||
$downloadDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "downloads/";
|
||||
|
||||
if (!is_dir($downloadDirectory)) {
|
||||
if (!mkdir($downloadDirectory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!move_uploaded_file($tmp_name, $downloadDirectory . $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->files[] = $name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine downloadbare Datei des Themas
|
||||
* @param string $name Dateiname
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function deleteDownload(string $name): bool
|
||||
{
|
||||
if (!isset($this->files[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unlink(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "downloads/$name")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->files = array_diff($this->files, [$name]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt eine Datei als Bild zum Thema hoch
|
||||
* @param string $name Dateiname von User, z.B. $_FILES['html-input-name']['name'][0]
|
||||
* @param string $tmp_name Temporärer Pfad zum hochgeladenen Bild, z.B. $_FILES['html-input-name']['tmp_name'][0]
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function addImage(string $name, string $tmp_name): bool
|
||||
{
|
||||
$imageDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/";
|
||||
|
||||
if (!is_dir($imageDirectory)) {
|
||||
if (!mkdir($imageDirectory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!move_uploaded_file($tmp_name, $imageDirectory . $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft für alle verwandten Themen, ob diese auch existieren. Wenn nicht, wird es aus der Liste entfernt
|
||||
* @return bool true, wenn Elemente entfernt wurden, sonst false
|
||||
*/
|
||||
private function cleanupRelatedTopics(): bool
|
||||
{
|
||||
$changed = false;
|
||||
$nonexistentEntries = array();
|
||||
|
||||
foreach ($this->relatedTopics as $topic) {
|
||||
if (!self::exists($this->subjectId, $topic)) {
|
||||
$nonexistentEntries[] = $topic;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->relatedTopics = array_diff($this->relatedTopics, $nonexistentEntries);
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft für alle Downloads, ob die zugehörige Datei existiert und ob zu jeder Datei ein Eintrag existiert.
|
||||
* Wenn eine Datei nicht existiert, wird auch der zugehörige Eintrag entfernt.
|
||||
* Wenn ein Eintrag nicht existiert, wird auch die Datei gelöscht.
|
||||
* @return bool true, wenn etwas verändert wurde
|
||||
*/
|
||||
private function cleanupFiles(): bool
|
||||
{
|
||||
$changed = false;
|
||||
|
||||
$nonexistentEntries = array();
|
||||
foreach ($this->files as $file) {
|
||||
if(!file_exists(Config::getTopicDirectory($this->subjectId, $this->id) . "downloads/$file")) {
|
||||
$nonexistentEntries[] = $file;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
$this->files = array_diff($this->files, $nonexistentEntries);
|
||||
|
||||
foreach (Util::getFilesFromDirectory(Config::getTopicDirectory($this->subjectId, $this->id) . "downloads/") as $file) {
|
||||
if(!array_search($file, $this->files)) {
|
||||
$this->deleteDownload($file);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht ein Bild des Themas
|
||||
* @param string $name Dateiname
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function deleteImage(string $name): bool
|
||||
{
|
||||
if (!unlink(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/$name")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht das Thema inklusive aller zugehörigen Dateien
|
||||
* @return bool true, wenn erfolgreich gelöscht, sonst false
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if (!Util::delete(Config::getTopicDirectory($this->getSubjectId(), $this->getId()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(string $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getSubjectId(): string
|
||||
{
|
||||
return $this->subjectId;
|
||||
}
|
||||
|
||||
public function setSubjectId(string $subjectId): void
|
||||
{
|
||||
$this->subjectId = $subjectId;
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function setDisplayName(string $displayName): void
|
||||
{
|
||||
$this->displayName = $displayName;
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function setIcon(string $icon): void
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function getRelatedTopics(): array
|
||||
{
|
||||
return $this->relatedTopics;
|
||||
}
|
||||
|
||||
public function setRelatedTopics(array $relatedTopics): void
|
||||
{
|
||||
$this->relatedTopics = $relatedTopics;
|
||||
}
|
||||
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
public function setFiles(array $files): void
|
||||
{
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
public function getArticle(): string
|
||||
{
|
||||
return $this->article;
|
||||
}
|
||||
|
||||
public function setArticle(string $article): void
|
||||
{
|
||||
$this->article = $article;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
277
webseite/classes/User.php
Normal file
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
require_once("Util.php");
|
||||
|
||||
/**
|
||||
* Repräsentiert einen Nutzer inklusive aller Nutzerdaten
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @var string Pfad zum Verzeichnis der Nutzerdaten
|
||||
*/
|
||||
private static string $userdataDirectory = "./users/";
|
||||
|
||||
/**
|
||||
* @var string Dateiname der Benutzerdaten
|
||||
*/
|
||||
private static string $userdataFile = "users.csv";
|
||||
|
||||
/**
|
||||
* @var string Benutzername, darf A-Z, a-z, 0-9 sowie - und _ enthalten, max. 100 Zeichen
|
||||
*/
|
||||
private string $username;
|
||||
|
||||
/**
|
||||
* @var string Passworthash
|
||||
*/
|
||||
private string $passwordHash;
|
||||
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Benutzer
|
||||
* @param string $username Benutzername, darf A-Z, a-z, 0-9 sowie - und _ enthalten, max. 100 Zeichen
|
||||
* @param string $password Passwort, max. 100 Zeichen
|
||||
* @return User|false Den neu erstellten Benutzer oder false, wenn der Benutzer nicht erstellt werden konnte
|
||||
*/
|
||||
public static function createUser(string $username, string $password): User|false
|
||||
{
|
||||
if(self::getFromUsername($username) !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strlen($username) > 100) {
|
||||
return false;
|
||||
}
|
||||
if(strlen($password) > 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Util::containsIllegalCharacters($username)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_ARGON2I);
|
||||
|
||||
if(!is_dir(self::$userdataDirectory)) {
|
||||
mkdir(self::$userdataDirectory);
|
||||
}
|
||||
|
||||
$file = fopen(self::$userdataDirectory . self::$userdataFile, "a");
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fputcsv($file, array($username, $passwordHash),",", '"', "\\");
|
||||
fclose($file);
|
||||
|
||||
return self::getFromUsername($username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt einen Benutzer anhand eines Benutzernamen zurück
|
||||
* @param string $username Benutzername nach dem gesucht wird
|
||||
* @return User|false Der gefundene Besucher oder false, wenn der Benutzer nicht gefunden wurde
|
||||
*/
|
||||
public static function getFromUsername(string $username): User|false
|
||||
{
|
||||
if (!file_exists(self::$userdataDirectory . self::$userdataFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = fopen(self::$userdataDirectory . self::$userdataFile, "r");
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (($data = fgetcsv($file, 300, ',', '"', '\\')) !== false) {
|
||||
if (count($data) != 2) {
|
||||
continue;
|
||||
}
|
||||
if ($data[0] !== $username) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->username = $data[0];
|
||||
$user->passwordHash = $data[1];
|
||||
return $user;
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht einen Benutzer
|
||||
* @param string $password Richtiges Passwort zu diesem Nutzer
|
||||
* @return bool true, wenn erfolgreich gelöscht, sonst false
|
||||
*/
|
||||
public function delete(string $password): bool
|
||||
{
|
||||
if(!$this->isPasswordCorrect($password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$this->logout()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = fopen(self::$userdataDirectory . self::$userdataFile, "r");
|
||||
if(!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newCsv = array();
|
||||
|
||||
while (($data = fgetcsv($file, 300, ',', '"', '\\')) !== false) {
|
||||
if (count($data) != 2) {
|
||||
continue;
|
||||
}
|
||||
if ($data[0] !== $this->username) {
|
||||
$newCsv[] = $data;
|
||||
}
|
||||
}
|
||||
fclose($file);
|
||||
|
||||
$file = fopen(self::$userdataDirectory . self::$userdataFile, "w");
|
||||
if(!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($newCsv as $newCsvData) {
|
||||
fputcsv($file, $newCsvData, ',', '"', '\\');
|
||||
}
|
||||
fclose($file);
|
||||
|
||||
unset($this->username);
|
||||
unset($this->passwordHash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert das Passwort des Accounts
|
||||
* @param string $oldPassword altes Passwort
|
||||
* @param string $newPassword Neues Passwort
|
||||
* @return bool true, wenn erfolgreich geändert, sonst false
|
||||
*/
|
||||
public function changePassword(string $oldPassword, string $newPassword): bool
|
||||
{
|
||||
if(!$this->isPasswordCorrect($oldPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$this->logout()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = fopen(self::$userdataDirectory . self::$userdataFile, "c+");
|
||||
if(!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->passwordHash = password_hash($newPassword, PASSWORD_ARGON2I);
|
||||
|
||||
$lastLine = ftell($file);
|
||||
while (($data = fgetcsv($file, 300, ',', '"', '\\')) !== false) {
|
||||
if (count($data) != 2) {
|
||||
|
||||
} else if ($data[0] !== $this->username) {
|
||||
|
||||
} else {
|
||||
$data[1] = $this->passwordHash;
|
||||
|
||||
fseek($file, $lastLine);
|
||||
|
||||
fputcsv($file, $data, ',', '"', '\\');
|
||||
break;
|
||||
}
|
||||
|
||||
$lastLine = ftell($file);
|
||||
}
|
||||
fclose($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Passwort für diesen Benutzer korrekt ist
|
||||
* @param string $password Zu prüfendes Passwort
|
||||
* @return bool true, wenn korrekt, sonst false
|
||||
*/
|
||||
public function isPasswordCorrect(string $password): bool
|
||||
{
|
||||
return password_verify($password, $this->passwordHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldet den Benutzer an, wenn das Passwort richtig ist
|
||||
* @param string $password Passwort
|
||||
* @return bool true, wenn erfolgreich, sonst false
|
||||
*/
|
||||
public function login(string $password): bool
|
||||
{
|
||||
if(!$this->isPasswordCorrect($password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SESSION["user"] = $this;
|
||||
$_SESSION["login_time"] = time();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Benutzer angemeldet ist
|
||||
* @return bool true, wenn angemeldet, sonst false
|
||||
*/
|
||||
public function isLoggedIn(): bool
|
||||
{
|
||||
if(!isset($_SESSION["user"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($_SESSION["user"] != $this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(time() - $_SESSION["login_time"] > 86400 * 5) {
|
||||
session_unset();
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SESSION["login_time"] = time();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldet den Benutzer ab
|
||||
* @return bool true, wenn erfolgreich abgemeldet, false, wenn vorher schon abgemeldet
|
||||
*/
|
||||
public function logout(): bool
|
||||
{
|
||||
if(!$this->isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session_unset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Benutzernamen zurück
|
||||
* @return string Benutzername
|
||||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,43 @@ class Util
|
||||
return preg_replace("/[^a-zA-Z0-9_-]/", "", $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob Zeichen außer A-Z, a-z, 0-9 sowie - und _ enthalten sind
|
||||
* @param string $string Zu prüfender Text
|
||||
* @return bool true, wenn ungültige Zeichen enthalten sind, sonst false
|
||||
*/
|
||||
static function containsIllegalCharacters(string $string): bool
|
||||
{
|
||||
if (preg_match("/[^a-zA-Z0-9_-]/", $string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static function getFilesFromDirectory(string $directory): array
|
||||
{
|
||||
$files = array();
|
||||
|
||||
if (is_dir($directory)) {
|
||||
$fileNames = scandir($directory);
|
||||
foreach ($fileNames as $fileName) {
|
||||
if ($fileName == "." || $fileName == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
$files[] = $fileName;
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest den gesamten Text aus einer Datei aus
|
||||
* @param string $filename Dateipfad
|
||||
* @return string|null Der enthaltene Text oder null bei einem Fehler
|
||||
*/
|
||||
static function readFileContent(string $filename): string|null
|
||||
{
|
||||
if (!file_exists($filename)) {
|
||||
@@ -33,6 +70,54 @@ class Util
|
||||
return $fileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt Inhalt in eine Datei. Überschreibt vorhandene Inhalte. Erstellt eine neue Datei, falls nötig.
|
||||
* @param string $filename Dateipfad
|
||||
* @param string $content Zu schreibender Text
|
||||
* @return bool true, wenn erfolgreich, false, wenn ein Fehler aufgetreten ist
|
||||
*/
|
||||
static function writeFileContent(string $filename, string $content): bool
|
||||
{
|
||||
$file = fopen($filename, "w");
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fwrite($file, $content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine Datei oder einen Ordner inklusive aller Inhalte
|
||||
* @param string $path Pfad zur Datei oder Verzeichnis
|
||||
* @return bool true, wenn gelöscht, sonst false
|
||||
*/
|
||||
static function delete(string $path): bool
|
||||
{
|
||||
if(is_file($path)) {
|
||||
if(!unlink($path)) {
|
||||
return false;
|
||||
}
|
||||
} else if(is_dir($path)) {
|
||||
$entries = scandir($path);
|
||||
foreach ($entries as $entry) {
|
||||
if($entry == "." || $entry == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::delete($path . "/" . $entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet eine Datei und gibt JSON-Inhalte als Array zurück
|
||||
* @param string $filename Dateipfad
|
||||
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Adverbiale Bestimmungen sind Satzteile, die zusätzliche Informationen über Umstände wie Zeit, Ort, Grund oder Art und Weise geben und dadurch die Handlung des Satzes genauer beschreiben.",
|
||||
"relatedTopics": [
|
||||
"wortarten", "vier-faelle"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 788 KiB After Width: | Height: | Size: 788 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Das Thema \"Geschichten erzählen\" umfasst das kreative Gestalten und Vermitteln von Erlebnissen oder Fantasien durch eine spannende Handlung, interessante Charaktere und lebendige Beschreibungen, um die Zuhörer oder Leser zu fesseln.",
|
||||
"relatedTopics": [
|
||||
"satzglieder", "personalpronomen"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf", "exercise2.pdf", "exercise3.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Personalpronomen sind Wörter, die anstelle von Personen oder Dingen verwendet werden, wie zum Beispiel \"ich\", \"du\", \"er\", \"sie\" oder \"es\", um Wiederholungen zu vermeiden und Sätze flüssiger zu gestalten.",
|
||||
"relatedTopics": [
|
||||
"wortarten", "geschichten-erzaehlen"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Satzglieder sind die Bausteine eines Satzes, die jeweils eine bestimmte Funktion erfüllen, wie Subjekt, Prädikat, Objekt oder adverbiale Bestimmung, und sich gemeinsam verschieben lassen, ohne die grammatische Korrektheit des Satzes zu verändern.",
|
||||
"relatedTopics": [
|
||||
"wortarten", "vier-faelle"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Die vier Fälle im Deutschen - Nominativ, Genitiv, Dativ und Akkusativ - beschreiben die verschiedenen grammatischen Funktionen eines Nomens oder Pronomens im Satz, wie Subjekt, Besitz, indirektes Objekt oder direktes Objekt.",
|
||||
"relatedTopics": [
|
||||
"satzglieder"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf", "exercise2.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 297 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Wortarten sind Kategorien, in die Wörter anhand ihrer grammatischen Funktion und Bedeutung eingeteilt werden, wie zum Beispiel Nomen, Verben, Adjektive und Adverbien.",
|
||||
"relatedTopics": [
|
||||
"satzglieder", "adverbiale-bestimmung", "personalpronomen"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 297 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Lorem Ipsum",
|
||||
"relatedTopics": [
|
||||
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Die Bruchrechnung ist ein Teil der Mathematik, der das Rechnen mit Brüchen beinhaltet, also das Teilen eines Ganzen in gleich große Teile, und umfasst Operationen wie Addition, Subtraktion, Multiplikation und Division von Brüchen.",
|
||||
"relatedTopics": [
|
||||
"schriftliches-multiplizieren", "schriftliches-dividieren", "punkt-vor-strichrechnung", "rechnen-mit-klammern"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf", "exercise2.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Die Regel \"Punkt vor Strichrechnung\" besagt, dass bei mathematischen Berechnungen Multiplikation und Division immer vor Addition und Subtraktion ausgeführt werden müssen, um das richtige Ergebnis zu erhalten.",
|
||||
"relatedTopics": [
|
||||
"rechnen-mit-klammern", "bruchrechnung"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Rechnen mit Einheiten bedeutet, Größen mit verschiedenen Maßeinheiten wie Meter, Kilogramm oder Liter rechnerisch zu verarbeiten, dabei die Einheiten korrekt umzurechnen und sicherzustellen, dass das Ergebnis in der richtigen Einheit angegeben wird.",
|
||||
"relatedTopics": [
|
||||
"schriftliches-dividieren", "bruchrechnung"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf", "exercise2.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Beim Rechnen mit Klammern werden die Rechenoperationen innerhalb der Klammern zuerst ausgeführt, bevor die restlichen Berechnungen im Ausdruck vorgenommen werden, um die korrekte Reihenfolge der Rechenschritte einzuhalten.",
|
||||
"relatedTopics": [
|
||||
"punkt-vor-strichrechnung", "bruchrechnung"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Schriftliches Dividieren ist eine Methode zur schrittweisen Aufteilung einer Zahl durch eine andere, wobei man die Teilschritte nacheinander schriftlich notiert, um das Ergebnis systematisch zu berechnen.",
|
||||
"relatedTopics": [
|
||||
"schriftliches-multiplizieren"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf", "exercise2.pdf", "exercise3.pdf", "exercise4.pdf"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -4,8 +4,5 @@
|
||||
"description": "Schriftliches Multiplizieren ist eine Rechenmethode, bei der zwei Zahlen schrittweise multipliziert werden, indem man die einzelnen Stellen der Zahlen nacheinander verrechnet, die Teilergebnisse notiert und am Ende addiert, um das Gesamtergebnis zu erhalten.",
|
||||
"relatedTopics": [
|
||||
"schriftliches-dividieren"
|
||||
],
|
||||
"files": [
|
||||
"exercise1.pdf"
|
||||
]
|
||||
}
|
||||
392
webseite/header.php
Normal file
@@ -0,0 +1,392 @@
|
||||
<!-- header.php -->
|
||||
<nav class="fixed top-0 w-full right-0 bg-white/80 backdrop-blur-lg shadow-sm z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<!-- Menu Toggle Button -->
|
||||
<div class="flex items-center space-x-4">
|
||||
|
||||
<button class="menu-toggle hidden text-[var(--primary-color)] border-2 border-[var(--primary-color)] w-10 h-10 flex items-center justify-center rounded-lg hover:bg-[var(--primary-color)] hover:text-white transition duration-300">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<a href="index.php" class="flex items-center">
|
||||
<img src="assets/images/hsgg-logo.png" alt="HSGG Logo" class="h-10 mr-3">
|
||||
<span class="text-2xl font-bold text-[var(--primary-color)]">HSGG</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Login/Logout Button -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Search Button -->
|
||||
<button id="openSearchDialog"
|
||||
class="bg-white text-[var(--primary-color)] border-2 border-[var(--primary-color)] w-10 h-10 flex items-center justify-center rounded-lg hover:bg-[var(--primary-color)] hover:text-white transition duration-300">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
|
||||
<?php
|
||||
require_once("classes/User.php");
|
||||
session_start();
|
||||
if (isset($_SESSION['user']) && $_SESSION['user']->isLoggedIn()) {
|
||||
?>
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Dropdown Button Elemente -->
|
||||
<div class="relative">
|
||||
<!-- Dropdown Trigger -->
|
||||
<button id="userDropdownToggle"
|
||||
class="bg-[var(--primary-color)] text-white px-4 py-2 rounded-lg hover:bg-[var(--accent-color)] transition duration-300 flex items-center">
|
||||
<span><?php echo htmlspecialchars($_SESSION['user']->getUsername()); ?></span>
|
||||
<i class="fas fa-chevron-down ml-2"></i>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div id="userDropdownMenu"
|
||||
class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border">
|
||||
<!-- TODO: Accountseite entsprechend verlinken -->
|
||||
<a href="index.php"
|
||||
class="block px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-t-lg flex items-center">
|
||||
<i class="fas fa-user mr-2"></i> Accountseite
|
||||
</a>
|
||||
<button id="dropdownChangePasswordButton"
|
||||
class="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-key mr-2"></i> Passwort ändern
|
||||
</button>
|
||||
<form id="dropdownLogoutForm" action="logout.php" method="POST" class="block">
|
||||
<button type="submit"
|
||||
class="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-b-lg flex items-center">
|
||||
<i class="fas fa-sign-out-alt mr-2"></i> Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bearbeiten Button --> <!-- TODO: Korrekte/dynamische Verlinkung implementieren -->
|
||||
<a href="index.php" class="bg-white text-[var(--primary-color)] border-2 border-[var(--primary-color)] w-10 h-10 flex items-center justify-center rounded-lg hover:bg-[var(--primary-color)] hover:text-white transition duration-300">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
// Login Button
|
||||
echo '<button id="loginButton" class="bg-white text-[var(--primary-color)] border-2 border-[var(--primary-color)] w-10 h-10 flex items-center justify-center rounded-lg hover:bg-[var(--primary-color)] hover:text-white transition duration-300">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
</button>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Login Popup | erscheint nur, wenn kein Nutzer eingeloggt ist -->
|
||||
<?php
|
||||
if (!isset($_SESSION['user']) || !$_SESSION['user']->isLoggedIn()) {
|
||||
?>
|
||||
<div id="loginPopup" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50" role="dialog"
|
||||
aria-labelledby="loginTitle" aria-hidden="true">
|
||||
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-sm relative">
|
||||
<button id="closePopupButton" class="absolute top-2 right-2 text-gray-500 text-xl"
|
||||
aria-label="Close Login Popup">×
|
||||
</button>
|
||||
<h2 id="loginTitle" class="text-2xl font-bold mb-6 text-center">Login</h2>
|
||||
<form id="loginForm" action="login.php" method="POST">
|
||||
<div class="mb-4">
|
||||
<label for="username" class="block text-gray-700 mb-2">Benutzername:</label>
|
||||
<input type="text" id="username" name="username" class="w-full p-2 border rounded-lg" required
|
||||
autofocus>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="password" class="block text-gray-700 mb-2">Passwort:</label>
|
||||
<input type="password" id="password" name="password" class="w-full p-2 border rounded-lg" required>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="w-full bg-[var(--primary-color)] text-white px-4 py-2 rounded-lg hover:bg-[var(--accent-color)] transition duration-300">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
<div id="errorMessage" class="hidden text-red-500 text-center mt-4">Falscher Benutzername oder Passwort
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Passwort-ändern-Popup -->
|
||||
<?php
|
||||
if (isset($_SESSION['user']) && $_SESSION['user']->isLoggedIn()) {
|
||||
?>
|
||||
<div id="changePasswordPopup" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
||||
role="dialog"
|
||||
aria-labelledby="changePasswordTitle" aria-hidden="true">
|
||||
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-sm relative">
|
||||
<button id="closeChangePasswordPopupButton" class="absolute top-2 right-2 text-gray-500 text-xl"
|
||||
aria-label="Close Change Password Popup">×
|
||||
</button>
|
||||
<h2 id="changePasswordTitle" class="text-2xl font-bold mb-6 text-center">Passwort ändern</h2>
|
||||
<form id="changePasswordForm" action="password.php" method="POST">
|
||||
<div class="mb-4">
|
||||
<label for="currentPassword" class="block text-gray-700 mb-2">Aktuelles Passwort:</label>
|
||||
<input type="password" id="currentPassword" name="currentPassword"
|
||||
class="w-full p-2 border rounded-lg" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="newPassword" class="block text-gray-700 mb-2">Neues Passwort:</label>
|
||||
<input type="password" id="newPassword" name="newPassword" class="w-full p-2 border rounded-lg"
|
||||
required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="confirmNewPassword" class="block text-gray-700 mb-2">Neues Passwort bestätigen:</label>
|
||||
<input type="password" id="confirmNewPassword" name="confirmNewPassword"
|
||||
class="w-full p-2 border rounded-lg" required>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="w-full bg-[var(--primary-color)] text-white px-4 py-2 rounded-lg hover:bg-[var(--accent-color)] transition duration-300">
|
||||
Passwort ändern
|
||||
</button>
|
||||
</form>
|
||||
<div id="changePasswordErrorMessage" class="hidden text-red-500 text-center mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Success Message Popup -->
|
||||
<div id="passwordSuccessPopup" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-sm text-center">
|
||||
<h2 class="text-xl font-bold text-green-600 mb-4">Erfolg!</h2>
|
||||
<p>Passwort wurde erfolgreich geändert.</p>
|
||||
<button id="closeSuccessPopup" class="mt-4 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Dialog -->
|
||||
<div id="searchDialog" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-lg relative">
|
||||
<button id="closeSearchDialog" class="absolute top-2 right-2 text-gray-500 text-xl">×</button>
|
||||
<h2 class="text-2xl font-bold mb-6 text-center">Suche</h2>
|
||||
<input type="text" id="searchInput" placeholder="Search..." class="w-full p-2 border rounded-lg mb-4">
|
||||
<div id="searchResults" class="max-h-64 overflow-y-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// JavaScript to handle opening and closing of the login popup
|
||||
const loginButton = document.getElementById('loginButton');
|
||||
const loginPopup = document.getElementById('loginPopup');
|
||||
const closePopupButton = document.getElementById('closePopupButton');
|
||||
const usernameInput = document.getElementById('username');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
if (loginButton) { // Überprüfen, ob das Element vorhanden ist
|
||||
loginButton.addEventListener('click', function () {
|
||||
loginPopup.classList.remove('hidden');
|
||||
usernameInput.focus(); // Set focus to username field
|
||||
});
|
||||
}
|
||||
|
||||
if (closePopupButton) { // Überprüfen, ob das Element vorhanden ist
|
||||
closePopupButton.addEventListener('click', function () {
|
||||
loginPopup.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('click', function (event) {
|
||||
if (event.target === loginPopup) {
|
||||
loginPopup.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Schließe Popup mit ESC
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === "Escape" && !loginPopup.classList.contains('hidden')) {
|
||||
loginPopup.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Zeige Fehlermeldung beim Login an
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('error') && urlParams.get('error') === '1') {
|
||||
loginPopup.classList.remove('hidden');
|
||||
errorMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// JavaScript to handle opening and closing of the change password popup
|
||||
const changePasswordButton = document.getElementById('changePasswordButton');
|
||||
const changePasswordPopup = document.getElementById('changePasswordPopup');
|
||||
const closeChangePasswordPopupButton = document.getElementById('closeChangePasswordPopupButton');
|
||||
|
||||
if (changePasswordButton) { // Überprüfen, ob das Element vorhanden ist
|
||||
changePasswordButton.addEventListener('click', function () {
|
||||
if (changePasswordPopup) {
|
||||
changePasswordPopup.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (closeChangePasswordPopupButton) {
|
||||
closeChangePasswordPopupButton.addEventListener('click', function () {
|
||||
if (changePasswordPopup) {
|
||||
changePasswordPopup.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('click', function (event) {
|
||||
if (event.target === changePasswordPopup) {
|
||||
changePasswordPopup.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Close popup with ESC key
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === "Escape" && !changePasswordPopup.classList.contains('hidden')) {
|
||||
changePasswordPopup.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Zeige Fehlermeldung beim Passwort ändern an
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.has('password_error')) {
|
||||
const changePasswordPopup = document.getElementById('changePasswordPopup');
|
||||
const changePasswordErrorMessage = document.getElementById('changePasswordErrorMessage');
|
||||
const errorType = urlParams.get('password_error');
|
||||
|
||||
changePasswordPopup.classList.remove('hidden');
|
||||
changePasswordErrorMessage.classList.remove('hidden');
|
||||
|
||||
switch (errorType) {
|
||||
case 'wrong_current_password':
|
||||
changePasswordErrorMessage.textContent = 'Das aktuelle Passwort ist falsch.';
|
||||
break;
|
||||
case 'password_mismatch':
|
||||
changePasswordErrorMessage.textContent = 'Die neuen Passwörter stimmen nicht überein.';
|
||||
break;
|
||||
default:
|
||||
changePasswordErrorMessage.textContent = 'Fehler beim Ändern des Passworts.';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Zeige Erfolgspopup beim Passwortwechsel an
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.has('password_success')) {
|
||||
const passwordSuccessPopup = document.getElementById('passwordSuccessPopup');
|
||||
if (passwordSuccessPopup) {
|
||||
passwordSuccessPopup.classList.remove('hidden');
|
||||
}
|
||||
const closeSuccessPopup = document.getElementById('closeSuccessPopup');
|
||||
if (closeSuccessPopup) {
|
||||
closeSuccessPopup.addEventListener('click', () => {
|
||||
passwordSuccessPopup.classList.add('hidden');
|
||||
// Optional: Entferne den URL-Parameter ohne Neuladen
|
||||
const newUrl = window.location.href.split('?')[0];
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Dropdown öffnen/schließen
|
||||
const userDropdownToggle = document.getElementById('userDropdownToggle');
|
||||
const userDropdownMenu = document.getElementById('userDropdownMenu');
|
||||
|
||||
if (userDropdownToggle && userDropdownMenu) {
|
||||
userDropdownToggle.addEventListener('click', (event) => {
|
||||
event.stopPropagation(); // Verhindert das Schließen des Menüs bei Klick auf den Button
|
||||
userDropdownMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Schließe Dropdown, wenn außerhalb geklickt wird
|
||||
window.addEventListener('click', () => {
|
||||
if (!userDropdownMenu.classList.contains('hidden')) {
|
||||
userDropdownMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Schließe Dropdown mit ESC
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === "Escape" && !userDropdownMenu.classList.contains('hidden')) {
|
||||
userDropdownMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Passwort ändern über Dropdown öffnen
|
||||
const dropdownChangePasswordButton = document.getElementById('dropdownChangePasswordButton');
|
||||
if (dropdownChangePasswordButton) {
|
||||
dropdownChangePasswordButton.addEventListener('click', () => {
|
||||
const changePasswordPopup = document.getElementById('changePasswordPopup');
|
||||
if (changePasswordPopup) {
|
||||
changePasswordPopup.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('openSearchDialog').addEventListener('click', function () {
|
||||
document.getElementById('searchDialog').classList.remove('hidden');
|
||||
});
|
||||
|
||||
document.getElementById('closeSearchDialog').addEventListener('click', function () {
|
||||
document.getElementById('searchDialog').classList.add('hidden');
|
||||
});
|
||||
|
||||
function debounce(func, delay) {
|
||||
let debounceTimer;
|
||||
return function () {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => func.apply(context, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const resultsContainer = document.getElementById('searchResults');
|
||||
|
||||
searchInput.addEventListener('input', debounce(function () {
|
||||
const query = this.value.toLowerCase();
|
||||
resultsContainer.innerHTML = '';
|
||||
|
||||
if (query.length > 0) {
|
||||
fetch('search.php?query=' + query)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
data.forEach(item => {
|
||||
const resultItem = document.createElement('div');
|
||||
resultItem.classList.add('p-4', 'mb-2', 'rounded-lg', 'bg-white', 'hover:bg-gray-100', 'transition', 'duration-100', 'flex', 'items-center', 'space-x-2', 'cursor-pointer');
|
||||
|
||||
const subjectSpan = document.createElement('span');
|
||||
subjectSpan.classList.add('font-bold');
|
||||
subjectSpan.textContent = item.displayName.split(' - ')[0];
|
||||
|
||||
const breadcrumbSpan = document.createElement('span');
|
||||
breadcrumbSpan.classList.add('text-gray-500');
|
||||
breadcrumbSpan.textContent = item.displayName.split(' - ').slice(1).join(' > ');
|
||||
|
||||
resultItem.appendChild(subjectSpan);
|
||||
resultItem.appendChild(breadcrumbSpan);
|
||||
|
||||
resultItem.addEventListener('click', function () {
|
||||
if (item.type === 'subject') {
|
||||
window.location.href = 'subject.php?subject=' + item.id;
|
||||
} else {
|
||||
window.location.href = 'topic.php?subject=' + item.subjectId + '&topic=' + item.id;
|
||||
}
|
||||
});
|
||||
|
||||
resultsContainer.appendChild(resultItem);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, 300));
|
||||
|
||||
</script>
|
||||
@@ -16,16 +16,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed top-0 w-full bg-white/80 backdrop-blur-lg shadow-sm z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<a href="index.php" class="flex items-center">
|
||||
<img src="assets/images/hsgg-logo.png" alt="HSGG Logo" class="h-10 mr-3">
|
||||
<span class="text-2xl font-bold text-[var(--primary-color)]">HSGG</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<div class="content mt-24 max-w-7xl mx-[16px] xl:mx-auto">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-6">Impressum</h1>
|
||||
|
||||
@@ -21,17 +21,7 @@
|
||||
<div class="blob absolute w-96 h-96 bg-sky-300/30 bottom-0 right-0"></div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed top-0 w-full bg-white/80 backdrop-blur-lg shadow-sm z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<a href="index.php" class="flex items-center">
|
||||
<img src="assets/images/hsgg-logo.png" alt="HSGG Logo" class="h-10 mr-3">
|
||||
<span class="text-2xl font-bold text-[var(--primary-color)]">HSGG</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<div class="hidden md:block pt-24 px-4">
|
||||
|
||||
24
webseite/login.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once("classes/User.php");
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
|
||||
// $testuser = User::createUser("MAX", "password");
|
||||
|
||||
// Try to retrieve the user from the username
|
||||
$user = User::getFromUsername($username);
|
||||
|
||||
if ($user && $user->login($password)) {
|
||||
// Redirect to a protected page if login is successful
|
||||
header("Location: index.php?login=success");
|
||||
|
||||
} else {
|
||||
// Redirect back to the login page with an error message
|
||||
header("Location: index.php?error=1");
|
||||
}
|
||||
}
|
||||
?>
|
||||
22
webseite/logout.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
require_once("classes/User.php");
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
|
||||
$user = $_SESSION['user'];
|
||||
if ($user->logout()) {
|
||||
// Logout successful, redirect to homepage
|
||||
header("Location: index.php");
|
||||
exit();
|
||||
} else {
|
||||
// Logout failed, handle error
|
||||
echo "<script>alert('Logout failed. Please try again.'); window.location.href='index.php';</script>";
|
||||
}
|
||||
} else {
|
||||
// No user is logged in, redirect to homepage
|
||||
header("Location: index.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
39
webseite/password.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once("classes/User.php");
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!isset($_SESSION['user']) || !$_SESSION['user']->isLoggedIn()) {
|
||||
header("Location: index.php?password_error=not_logged_in");
|
||||
exit();
|
||||
}
|
||||
|
||||
$currentPassword = $_POST['currentPassword'];
|
||||
$newPassword = $_POST['newPassword'];
|
||||
$confirmNewPassword = $_POST['confirmNewPassword'];
|
||||
|
||||
$user = $_SESSION['user'];
|
||||
|
||||
// Prüfe, ob das aktuelle Passwort korrekt ist
|
||||
if (!$user->isPasswordCorrect($currentPassword)) {
|
||||
header("Location: index.php?password_error=wrong_current_password");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Prüfe, ob die neuen Passwörter übereinstimmen
|
||||
if ($newPassword !== $confirmNewPassword) {
|
||||
header("Location: index.php?password_error=password_mismatch");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Aktualisiere das Passwort
|
||||
if ($user->changePassword($currentPassword, $newPassword)) {
|
||||
header("Location: index.php?password_success=1");
|
||||
exit();
|
||||
} else {
|
||||
header("Location: index.php?password_error=update_failed");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
?>
|
||||
40
webseite/search.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
require_once("classes/SubjectData.php");
|
||||
require_once("classes/TopicData.php");
|
||||
|
||||
if (!isset($_GET['query'])) {
|
||||
die(json_encode([]));
|
||||
}
|
||||
|
||||
$query = strtolower(trim($_GET['query']));
|
||||
$subjects = SubjectData::getAll();
|
||||
$results = [];
|
||||
|
||||
foreach ($subjects as $subject) {
|
||||
if (strpos(strtolower($subject->displayName), $query) !== false) {
|
||||
$results[] = [
|
||||
'type' => 'subject',
|
||||
'id' => $subject->id,
|
||||
'displayName' => $subject->displayName
|
||||
];
|
||||
}
|
||||
foreach ($subject->topics as $topic) {
|
||||
if (
|
||||
strpos(strtolower($subject->displayName), $query) !== false ||
|
||||
strpos(strtolower($topic->displayName), $query) !== false ||
|
||||
strpos(strtolower($topic->description), $query) !== false ||
|
||||
strpos(strtolower($topic->article), $query) !== false
|
||||
) {
|
||||
$results[] = [
|
||||
'type' => 'topic',
|
||||
'subjectId' => $topic->subjectId,
|
||||
'id' => $topic->id,
|
||||
'displayName' => $subject->displayName . ' - ' . $topic->displayName
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($results);
|
||||
?>
|
||||
@@ -31,8 +31,9 @@ $topics = $subjectData->topics;
|
||||
|
||||
|
||||
<body class="min-h-screen">
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<nav class="sidebar bg-[<?php echo($subjectData->color); ?>]">
|
||||
<nav class="sidebar bg-[<?php echo($subjectData->color); ?>] pt-24">
|
||||
<div class="sidebar-header">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
|
||||
@@ -47,15 +48,6 @@ $topics = $subjectData->topics;
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="search-container bg-white">
|
||||
<button class="menu-toggle h-12 w-12 border-2 p-1 hover:border-[<?php echo($subjectData->color); ?>]">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<input type="text" class="search-box p-3 border-2 w-full focus:border-[<?php echo($subjectData->color); ?>]"
|
||||
id="searchInput" placeholder="Suche nach Themen, Übungen oder Hilfe..."
|
||||
oninput="handleSearch()">
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="max-w-7xl mx-auto px-4 py-12 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-8 mb-8">
|
||||
|
||||
@@ -80,13 +72,13 @@ $topics = $subjectData->topics;
|
||||
<h4>Verwandte Themen:</h4>
|
||||
<ul>
|
||||
<?php
|
||||
foreach ($topicData->relatedTopics as $relatedTopicName) {
|
||||
$relatedTopic = $subjectData->topics[$relatedTopicName];
|
||||
if (!isset($relatedTopicName)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($topicData->relatedTopics as $relatedTopicName) {
|
||||
$relatedTopic = $subjectData->topics[$relatedTopicName];
|
||||
if (!isset($relatedTopicName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
?>
|
||||
?>
|
||||
|
||||
<li onclick="event.stopPropagation();" class="border-[<?php echo($subjectData->color); ?>] border-2">
|
||||
<a href='<?php echo("topic.php?subject=$subjectData->id&topic=$relatedTopic->id") ?>'>
|
||||
@@ -95,8 +87,8 @@ $topics = $subjectData->topics;
|
||||
</li>
|
||||
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
</div>-->
|
||||
</div>
|
||||
@@ -108,7 +100,7 @@ $topics = $subjectData->topics;
|
||||
?>
|
||||
|
||||
<a onclick="event.stopPropagation();"
|
||||
href="<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/$fileName") ?>"
|
||||
href="<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/downloads/$fileName") ?>"
|
||||
target="_blank" download
|
||||
class="download-btn border-2 border-[<?php echo($subjectData->color); ?>]">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
|
||||
@@ -36,13 +36,10 @@ if (!isset($topicData)) {
|
||||
|
||||
|
||||
<body class="min-h-screen">
|
||||
|
||||
<button class="menu-toggle fixed top-4 left-4 z-50 bg-white h-12 w-12 border-2 p-1 hover:border-[<?php echo($subjectData->color); ?>]">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<!-- Left Sidebar -->
|
||||
<nav class="sidebar bg-[<?php echo($subjectData->color); ?>]">
|
||||
<nav class="sidebar bg-[<?php echo($subjectData->color); ?>] pt-24">
|
||||
<div class="sidebar-header">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
<h2><?php echo($subjectData->displayName); ?></h2>
|
||||
@@ -102,7 +99,7 @@ if (!isset($topicData)) {
|
||||
foreach ($topicData->files as $fileName) {
|
||||
?>
|
||||
|
||||
<a href='<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/$fileName") ?>'
|
||||
<a href='<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/downloads/$fileName") ?>'
|
||||
target="_blank" download
|
||||
class="download-btn border-[<?php echo($subjectData->color); ?>] border-2">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
|
||||
2
webseite/users/users.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
admin,"$argon2i$v=19$m=65536,t=4,p=1$UVN1S1loTmxVSEdqTjVFcQ$J2sL51VLx7Deg7xJRnbrKfIVqGduh+nrGOFGFGSZ4vw"
|
||||
MAX,"$argon2i$v=19$m=65536,t=4,p=1$UVN1S1loTmxVSEdqTjVFcQ$J2sL51VLx7Deg7xJRnbrKfIVqGduh+nrGOFGFGSZ4vw"
|
||||
|