593 lines
17 KiB
PHP
593 lines
17 KiB
PHP
<?php
|
|
|
|
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();
|
|
|
|
if(!is_dir(Config::getTopicsDirectory($subjectId))) {
|
|
return array();
|
|
}
|
|
|
|
$topicNames = scandir(Config::getTopicsDirectory($subjectId));
|
|
if(!$topicNames) {
|
|
return array();
|
|
}
|
|
|
|
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 (!in_array($name, $this->files)) {
|
|
echo "a";
|
|
return false;
|
|
}
|
|
|
|
if (!unlink(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "downloads/$name")) {
|
|
echo "b";
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Erstellt eine Datei aus übergebenen Daten
|
|
* @param string $name Dateiname
|
|
* @param string $image Bilddaten als Dateiinhalt, z.B. von file_get_contents()
|
|
* @return bool true wenn erfolgreich, sonst false
|
|
*/
|
|
public function uploadImage(string $name, string $image): bool
|
|
{
|
|
$imageDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/";
|
|
|
|
if (!is_dir($imageDirectory)) {
|
|
if (!mkdir($imageDirectory)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!file_put_contents("$imageDirectory/$name", $image)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Löscht alle derzeit gespeicherten Bilder des Themas
|
|
* @return bool true wenn erfolgreich, sonst false
|
|
*/
|
|
public function deleteAllImages(): bool
|
|
{
|
|
return Util::delete(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/");
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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 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 removeAllTasks()
|
|
{
|
|
$this->tasks = array();
|
|
}
|
|
|
|
public function getId(): string
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function setId(string $id): void
|
|
{
|
|
rename(
|
|
Config::getTopicDirectory($this->getSubjectId(), $this->getId()),
|
|
Config::getTopicDirectory($this->getSubjectId(), $id)
|
|
);
|
|
|
|
$this->id = $id;
|
|
}
|
|
|
|
public function getSubjectId(): string
|
|
{
|
|
return $this->subjectId;
|
|
}
|
|
|
|
public function setSubjectId(string $subjectId): void
|
|
{
|
|
rename(
|
|
Config::getTopicDirectory($this->getSubjectId(), $this->getId()),
|
|
Config::getTopicDirectory($subjectId, $this->getId())
|
|
);
|
|
|
|
$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
|
|
{
|
|
$a = str_replace('__TOPICPATH__', Config::getTopicDirectory($this->subjectId, $this->id) . "images", $this->article);
|
|
|
|
return $a;
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |