Dashboard Fach hinzufügen funktioniert in swe-b1-a-dev

This commit is contained in:
Kelvi Yawo Jules Agbessi Awuklu
2024-12-21 20:08:59 +01:00
committed by Matthias Grief
parent b2cff02086
commit 6f73884baf
119 changed files with 5374 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?php
/**
* Konfigurationsdaten
*/
class Config
{
/**
* @return string Verzeichnis für generelle Konfigurationsdaten
*/
public static function getConfigDirectory(): string
{
// Get absolute path to webseite directory
$webseiteRoot = dirname(__DIR__) . '/';
return $webseiteRoot . "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 . "/";
}
}

View File

@@ -0,0 +1,309 @@
<?php
require_once("Config.php");
require_once("Util.php");
require_once("TopicData.php");
/**
* Stellt alle relevanten Daten für ein einzelnes Fach bereit
*
*/
class SubjectData
{
/**
* @var string Ein eindeutiger Bezeichner für das Fach, darf nur A-Z, a-Z, 0-9 sowie _ und - enthalten
*/
public string $id;
/**
* @var string Der für User angezeigte Name des Faches, nur reiner Text
*/
public string $displayName;
/**
* @var string Eine kurze Beschreibung des Faches, z.B. für den Text auf der Startseite, kann HTML enthalten
*/
public string $description;
/**
* @var string Themenfarbe des Faches als hexcode
*/
public string $color;
/**
* @var string Icon des Faches als Font-Awesome CSS-Klasse
*/
public string $icon;
/**
* @var array Alle Themen des Faches als TopicData Objekt
* @see TopicData
*/
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
*/
public static function getAll(): array
{
$result = array();
$subjectNames = scandir(Config::getSubjectsDirectory());
usort($subjectNames, function ($a, $b) {
return strcmp($a, $b);
});
foreach ($subjectNames as $subjectName) {
if ($subjectName == "." || $subjectName == "..") {
continue;
}
$subjectData = SubjectData::fromName($subjectName);
if (!isset($subjectData)) {
continue;
}
$result[$subjectData->id] = $subjectData;
}
return $result;
}
/**
* Lädt ein Fach über eine gegebene ID
* @param $subjectId string Die eindeutige ID des Faches
* @return SubjectData|null Das Fach zu der ID oder null, wenn kein entsprechendes Fach gefunden wurde
*/
public static function fromName(string $subjectId): SubjectData|null
{
$result = new SubjectData();
if (Util::containsIllegalCharacters($subjectId)) {
return null;
}
$result->id = $subjectId;
$filename = Config::getSubjectDirectory($subjectId) . "properties.json";
$data = Util::parseJsonFromFile($filename);
if (!isset($data)) {
return null;
}
if (!isset($data->displayName)) {
return null;
}
$result->displayName = $data->displayName;
if (!isset($data->description)) {
return null;
}
$result->description = $data->description;
if (!isset($data->color)) {
return null;
}
$result->color = $data->color;
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
{
$subjectDirectory = Config::getSubjectDirectory($this->getId());
$topicsDirectory = Config::getTopicsDirectory($this->getId());
// Create directories if they don't exist
if (!is_dir($subjectDirectory)) {
if (!mkdir($subjectDirectory, 0777, true)) {
return false;
}
}
if (!is_dir($topicsDirectory)) {
if (!mkdir($topicsDirectory, 0777, true)) {
return false;
}
}
$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;
}
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;
}
}

View File

@@ -0,0 +1,48 @@
<?php
class Task
{
/**
* @var string Aufgabentext kann z.B. auch LaTeX-Notation enthalten
*/
private string $text;
/**
* @var array Alle verwendeten Variablen mit key als Variable und value als richtigen Wert
*/
private array $variables;
/**
* Erstellt eine neue Aufgabe
* @param string $text Aufgabentext, kann z.B. auch LaTeX-Notation enthalten
* @param array $variables Assoziatives Array mit Variable → Richtiger Wert
*/
public function __construct(string $text, array $variables)
{
$this->text = $text;
$this->variables = array();
foreach ($variables as $variable => $value) {
if(!is_string($value)) {
continue;
}
$this->variables[$variable] = $value;
}
}
/**
* @return string Aufgabentext
*/
public function getText(): string
{
return $this->text;
}
/**
* @return array Assoziatives Array mit Variable → Richtiger Wert
*/
public function getVariables(): array
{
return $this->variables;
}
}

View File

@@ -0,0 +1,544 @@
<?php
use exception\SubjectDoesNotExistException;
use exception\TopicAlreadyExistsException;
require_once("Task.php");
require_once("Config.php");
require_once("Util.php");
class TopicData
{
/**
* @var string Innerhalb des zugehörigen Faches eindeutige ID, darf nur A-Z, a-z, 0-9 sowie - und _ enthalten
*/
public string $id;
/**
* @var string Die eindeutige ID des zugehörigen Faches
*/
public string $subjectId;
/**
* @var string Der für User angezeigt Name des Themas, darf nur reinen Text enthalten
*/
public string $displayName;
/**
* @var string Das Icon des Themas als Font-Awesome CSS-Klasse
*/
public string $icon;
/**
* @var string Eine kurze Beschreibung des Themas, z.B. für die Fachübersichtsseite, darf HTML enthalten
*/
public string $description;
/**
* @var array Die IDs aller verwandten Themen als String
*/
public array $relatedTopics;
/**
* @var array Die Dateinamen (Datei.pdf) aller downloadbarer Dateien zu diesem Thema als String
*/
public array $files;
/**
* @var string 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
*/
private string $article;
/**
* @var array Alle zugehörigen Formelaufgaben als Task
* @see Task
*/
private array $tasks;
/**
* 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;
$result->tasks = array();
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
* @return array Alle zugehörigen Themen als TopicData Objekte
*/
public static function getAll(string $subjectId): array
{
$result = array();
$topicsDir = Config::getTopicsDirectory($subjectId);
// Return empty array if directory doesn't exist
if (!is_dir($topicsDir)) {
return $result;
}
$topicNames = scandir($topicsDir);
usort($topicNames, function ($a, $b) {
return strcmp($a, $b);
});
foreach ($topicNames as $topicName) {
if ($topicName == "." || $topicName == "..") {
continue;
}
$topicData = TopicData::fromName($subjectId, $topicName);
if (!isset($topicData)) {
continue;
}
$result[$topicData->id] = $topicData;
}
return $result;
}
/**
* Gibt Themendaten zu einem bestimmten Thema eines Faches zurück
* @param $subjectId string Die ID des Faches
* @param $topicId string Die ID des Themas
* @return TopicData|null Die Themendaten oder null, wenn das Thema nicht existiert
*/
public static function fromName(string $subjectId, string $topicId): TopicData|null
{
$result = new TopicData();
if (Util::containsIllegalCharacters($subjectId)) {
return null;
}
if (Util::containsIllegalCharacters($topicId)) {
return null;
}
$result->id = $topicId;
$result->subjectId = $subjectId;
$data = Util::parseJsonFromFile(Config::getTopicDirectory($subjectId, $topicId) . "properties.json");
if (!isset($data)) {
return null;
}
if (!isset($data->displayName)) {
return null;
}
$result->displayName = $data->displayName;
if (!isset($data->icon)) {
return null;
}
$result->icon = $data->icon;
if (!isset($data->description)) {
return null;
}
$result->description = $data->description;
$relatedTopics = array();
if (isset($data->relatedTopics)) {
$relatedTopics = $data->relatedTopics;
}
$result->relatedTopics = $relatedTopics;
$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";
}
$result->article = $article;
$taskJson = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "tasks.json");
$result->tasks = array();
if(isset($taskJson)) {
$arr = json_decode($taskJson, true);
foreach ($arr as $rawTask) {
$text = $rawTask["text"];
if(!isset($text)) {
continue;
}
$vars = $rawTask["vars"];
if (!isset($vars)) {
continue;
}
$result->tasks[] = new Task($text, $vars);
}
}
$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;
$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);
}
$taskArray = array();
foreach ($this->tasks as $task) {
$element = array();
$element["text"] = $task->getText();
$element["vars"] = $task->getVariables();
$taskArray[] = $element;
}
$taskJson = json_encode($taskArray, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
if (!$taskJson) {
return false;
}
if (!(Util::writeFileContent($topicDirectory . "properties.json", $json)
&& Util::writeFileContent($topicDirectory . "article.html", $this->article)
&& Util::writeFileContent($topicDirectory . "tasks.json", $taskJson)
)
) {
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 addTask(Task $task): bool
{
$this->tasks[] = $task;
return true;
}
public function removeTask(Task $task): bool
{
$this->tasks = array_diff($this->tasks, [$task]);
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;
}
/**
* Gibt anders als getArticle() Bildpfade richtig aus
* @return string HTML Quelltext für den Erklärtext
*/
public function getFinishedArticle(): string
{
return str_replace('$TOPICPATH', Config::getTopicDirectory($this->subjectId, $this->id) . "images", $this->article);
}
public function getArticle(): string
{
return $this->article;
}
public function setArticle(string $article): void
{
$this->article = $article;
}
public function getTasks(): array
{
return $this->tasks;
}
public function setTasks(array $tasks): void
{
$this->tasks = $tasks;
}
}

View 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;
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* Hilfsmethoden für verschiedene Aufgaben
*/
class Util
{
/**
* Entfernt alle Zeichen außer A-Z, a-z, 0-9 sowie - und _
* @param string $string Eingabe mit möglicherweise ungültigen Zeichen
* @return string Eingabestring mit allen ungültigen Zeichen entfernt
*/
static function removeIllegalCharacters(string $string): string
{
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)) {
return null;
}
$file = fopen($filename, "r");
if (!$file) {
return null;
}
$fileContent = fread($file, filesize($filename));
if (!$fileContent) {
return null;
}
fclose($file);
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
* @return mixed Array mit Key-Value Paaren
*/
static function parseJsonFromFile(string $filename): mixed
{
$content = self::readFileContent($filename);
if (!isset($content)) {
return null;
}
return json_decode($content);
}
}