Dashboard Fach hinzufügen funktioniert in swe-b1-a-dev
This commit is contained in:
committed by
Matthias Grief
parent
b2cff02086
commit
6f73884baf
53
swe-b1-a-dev/webseite/classes/Config.php
Normal file
53
swe-b1-a-dev/webseite/classes/Config.php
Normal 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 . "/";
|
||||
}
|
||||
}
|
||||
309
swe-b1-a-dev/webseite/classes/SubjectData.php
Normal file
309
swe-b1-a-dev/webseite/classes/SubjectData.php
Normal 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;
|
||||
}
|
||||
}
|
||||
48
swe-b1-a-dev/webseite/classes/Task.php
Normal file
48
swe-b1-a-dev/webseite/classes/Task.php
Normal 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;
|
||||
}
|
||||
}
|
||||
544
swe-b1-a-dev/webseite/classes/TopicData.php
Normal file
544
swe-b1-a-dev/webseite/classes/TopicData.php
Normal 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;
|
||||
}
|
||||
}
|
||||
277
swe-b1-a-dev/webseite/classes/User.php
Normal file
277
swe-b1-a-dev/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;
|
||||
}
|
||||
}
|
||||
134
swe-b1-a-dev/webseite/classes/Util.php
Normal file
134
swe-b1-a-dev/webseite/classes/Util.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user