From 7a146d89825b3044b22189dc133c294f210d965d Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Thu, 5 Dec 2024 17:49:41 +0100 Subject: [PATCH 01/11] Fach- und Themendaten umstrukturiert --- webseite/classes/Config.php | 51 ++++++++++++++++++++++++++++++++ webseite/classes/SubjectData.php | 13 ++++---- webseite/classes/TopicData.php | 13 ++++---- 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 webseite/classes/Config.php diff --git a/webseite/classes/Config.php b/webseite/classes/Config.php new file mode 100644 index 0000000..27e6f42 --- /dev/null +++ b/webseite/classes/Config.php @@ -0,0 +1,51 @@ +files = array(); } - $article = Util::readFileContent("$topicDataDirectory/article.html"); + $article = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "article.html"); if (!isset($article)) { $article = "Kein Erklärtext vorhanden"; } - $article = str_replace('$TOPICPATH', $topicDataDirectory, $article); + $article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId), $article); $result->article = $article; return $result; } - } \ No newline at end of file From e09438c6ea216db7c6e765c7e60afecd4c3bf9a1 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Fri, 6 Dec 2024 22:12:47 +0100 Subject: [PATCH 02/11] =?UTF-8?q?Speichern=20und=20l=C3=B6schen=20von=20Th?= =?UTF-8?q?emen=20und=20F=C3=A4chern=20eingebaut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webseite/classes/SubjectData.php | 102 ++++++++++++++++++++++++ webseite/classes/TopicData.php | 131 +++++++++++++++++++++++++++++++ webseite/classes/Util.php | 60 +++++++++++++- 3 files changed, 292 insertions(+), 1 deletion(-) diff --git a/webseite/classes/SubjectData.php b/webseite/classes/SubjectData.php index 82e6196..5dc2d7f 100644 --- a/webseite/classes/SubjectData.php +++ b/webseite/classes/SubjectData.php @@ -119,4 +119,106 @@ class SubjectData 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; + } + + 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; + } } \ No newline at end of file diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 65f1f17..0b27286 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -134,4 +134,135 @@ class TopicData 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["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ö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; + } + + } \ No newline at end of file diff --git a/webseite/classes/Util.php b/webseite/classes/Util.php index f118056..a8cabb8 100644 --- a/webseite/classes/Util.php +++ b/webseite/classes/Util.php @@ -15,15 +15,25 @@ 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)) { + if (preg_match("/[^a-zA-Z0-9_-]/", $string)) { return true; } return false; } + /** + * 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)) { @@ -42,6 +52,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 From 0bde9b3d241b2fc3900a6a0bb50041213ec68e8b Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Sun, 8 Dec 2024 15:17:00 +0100 Subject: [PATCH 03/11] Downloads zu Themen in eigene Ordner verschoben --- webseite/classes/TopicData.php | 16 ++++++++++++---- .../{ => downloads}/exercise1.pdf | Bin .../adverbiale-bestimmung/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../{ => downloads}/exercise2.pdf | Bin .../{ => downloads}/exercise3.pdf | Bin .../geschichten-erzaehlen/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../topics/personalpronomen/properties.json | 3 --- .../satzglieder/{ => downloads}/exercise1.pdf | Bin .../deutsch/topics/satzglieder/properties.json | 3 --- .../vier-faelle/{ => downloads}/exercise1.pdf | Bin .../vier-faelle/{ => downloads}/exercise2.pdf | Bin .../deutsch/topics/vier-faelle/properties.json | 3 --- .../wortarten/{ => downloads}/exercise1.pdf | Bin .../deutsch/topics/wortarten/properties.json | 3 --- .../LoremIpsum/{ => downloads}/exercise1.pdf | Bin .../englisch/topics/LoremIpsum/properties.json | 3 --- .../bruchrechnung/{ => downloads}/exercise1.pdf | Bin .../bruchrechnung/{ => downloads}/exercise2.pdf | Bin .../mathe/topics/bruchrechnung/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../punkt-vor-strichrechnung/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../{ => downloads}/exercise2.pdf | Bin .../rechnen-mit-einheiten/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../topics/rechnen-mit-klammern/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../{ => downloads}/exercise2.pdf | Bin .../{ => downloads}/exercise3.pdf | Bin .../{ => downloads}/exercise4.pdf | Bin .../schriftliches-dividieren/properties.json | 3 --- .../{ => downloads}/exercise1.pdf | Bin .../properties.json | 3 --- webseite/subject.php | 2 +- webseite/topic.php | 2 +- 37 files changed, 14 insertions(+), 45 deletions(-) rename webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/{ => downloads}/exercise2.pdf (100%) rename webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/{ => downloads}/exercise3.pdf (100%) rename webseite/config/subjects/deutsch/topics/personalpronomen/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/deutsch/topics/satzglieder/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/deutsch/topics/vier-faelle/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/deutsch/topics/vier-faelle/{ => downloads}/exercise2.pdf (100%) rename webseite/config/subjects/deutsch/topics/wortarten/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/englisch/topics/LoremIpsum/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/bruchrechnung/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/bruchrechnung/{ => downloads}/exercise2.pdf (100%) rename webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/{ => downloads}/exercise2.pdf (100%) rename webseite/config/subjects/mathe/topics/rechnen-mit-klammern/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/schriftliches-dividieren/{ => downloads}/exercise1.pdf (100%) rename webseite/config/subjects/mathe/topics/schriftliches-dividieren/{ => downloads}/exercise2.pdf (100%) rename webseite/config/subjects/mathe/topics/schriftliches-dividieren/{ => downloads}/exercise3.pdf (100%) rename webseite/config/subjects/mathe/topics/schriftliches-dividieren/{ => downloads}/exercise4.pdf (100%) rename webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/{ => downloads}/exercise1.pdf (100%) diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 0b27286..2411a71 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -118,10 +118,18 @@ class TopicData } else { $result->relatedTopics = array(); } - if (isset($data->files)) { - $result->files = $data->files; - } else { - $result->files = array(); + + $result->files = array(); + $downloadDirectory = Config::getTopicDirectory($subjectId, $topicId) . "downloads/"; + if(is_dir($downloadDirectory)) { + $fileNames = scandir($downloadDirectory); + foreach ($fileNames as $fileName) { + if ($fileName == "." || $fileName == "..") { + continue; + } + + $result->files[] = $fileName; + } } $article = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "article.html"); diff --git a/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/exercise1.pdf b/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/properties.json b/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/properties.json index eb6817d..854795c 100644 --- a/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/properties.json +++ b/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise1.pdf b/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise2.pdf b/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise2.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise2.pdf rename to webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise2.pdf diff --git a/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise3.pdf b/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise3.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/exercise3.pdf rename to webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/downloads/exercise3.pdf diff --git a/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/properties.json b/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/properties.json index 3372f25..a297d57 100644 --- a/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/properties.json +++ b/webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/deutsch/topics/personalpronomen/exercise1.pdf b/webseite/config/subjects/deutsch/topics/personalpronomen/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/personalpronomen/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/personalpronomen/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/personalpronomen/properties.json b/webseite/config/subjects/deutsch/topics/personalpronomen/properties.json index eec49d2..4f1f61b 100644 --- a/webseite/config/subjects/deutsch/topics/personalpronomen/properties.json +++ b/webseite/config/subjects/deutsch/topics/personalpronomen/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/deutsch/topics/satzglieder/exercise1.pdf b/webseite/config/subjects/deutsch/topics/satzglieder/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/satzglieder/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/satzglieder/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/satzglieder/properties.json b/webseite/config/subjects/deutsch/topics/satzglieder/properties.json index 90a0dfa..5c690bc 100644 --- a/webseite/config/subjects/deutsch/topics/satzglieder/properties.json +++ b/webseite/config/subjects/deutsch/topics/satzglieder/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/deutsch/topics/vier-faelle/exercise1.pdf b/webseite/config/subjects/deutsch/topics/vier-faelle/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/vier-faelle/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/vier-faelle/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/vier-faelle/exercise2.pdf b/webseite/config/subjects/deutsch/topics/vier-faelle/downloads/exercise2.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/vier-faelle/exercise2.pdf rename to webseite/config/subjects/deutsch/topics/vier-faelle/downloads/exercise2.pdf diff --git a/webseite/config/subjects/deutsch/topics/vier-faelle/properties.json b/webseite/config/subjects/deutsch/topics/vier-faelle/properties.json index b76669a..5339b63 100644 --- a/webseite/config/subjects/deutsch/topics/vier-faelle/properties.json +++ b/webseite/config/subjects/deutsch/topics/vier-faelle/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/deutsch/topics/wortarten/exercise1.pdf b/webseite/config/subjects/deutsch/topics/wortarten/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/deutsch/topics/wortarten/exercise1.pdf rename to webseite/config/subjects/deutsch/topics/wortarten/downloads/exercise1.pdf diff --git a/webseite/config/subjects/deutsch/topics/wortarten/properties.json b/webseite/config/subjects/deutsch/topics/wortarten/properties.json index 66f0c51..422354e 100644 --- a/webseite/config/subjects/deutsch/topics/wortarten/properties.json +++ b/webseite/config/subjects/deutsch/topics/wortarten/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/englisch/topics/LoremIpsum/exercise1.pdf b/webseite/config/subjects/englisch/topics/LoremIpsum/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/englisch/topics/LoremIpsum/exercise1.pdf rename to webseite/config/subjects/englisch/topics/LoremIpsum/downloads/exercise1.pdf diff --git a/webseite/config/subjects/englisch/topics/LoremIpsum/properties.json b/webseite/config/subjects/englisch/topics/LoremIpsum/properties.json index df380f7..5caecd9 100644 --- a/webseite/config/subjects/englisch/topics/LoremIpsum/properties.json +++ b/webseite/config/subjects/englisch/topics/LoremIpsum/properties.json @@ -4,8 +4,5 @@ "description": "Lorem Ipsum", "relatedTopics": [ - ], - "files": [ - "exercise1.pdf" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/bruchrechnung/exercise1.pdf b/webseite/config/subjects/mathe/topics/bruchrechnung/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/bruchrechnung/exercise1.pdf rename to webseite/config/subjects/mathe/topics/bruchrechnung/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/bruchrechnung/exercise2.pdf b/webseite/config/subjects/mathe/topics/bruchrechnung/downloads/exercise2.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/bruchrechnung/exercise2.pdf rename to webseite/config/subjects/mathe/topics/bruchrechnung/downloads/exercise2.pdf diff --git a/webseite/config/subjects/mathe/topics/bruchrechnung/properties.json b/webseite/config/subjects/mathe/topics/bruchrechnung/properties.json index 4b9c4b8..dd4e274 100644 --- a/webseite/config/subjects/mathe/topics/bruchrechnung/properties.json +++ b/webseite/config/subjects/mathe/topics/bruchrechnung/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/exercise1.pdf b/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/exercise1.pdf rename to webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/properties.json b/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/properties.json index bcc3e3f..9a958cf 100644 --- a/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/properties.json +++ b/webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/exercise1.pdf b/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/exercise1.pdf rename to webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/exercise2.pdf b/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/downloads/exercise2.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/exercise2.pdf rename to webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/downloads/exercise2.pdf diff --git a/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/properties.json b/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/properties.json index 010a643..b85e3ad 100644 --- a/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/properties.json +++ b/webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/exercise1.pdf b/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/rechnen-mit-klammern/exercise1.pdf rename to webseite/config/subjects/mathe/topics/rechnen-mit-klammern/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/properties.json b/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/properties.json index 016563e..6d50956 100644 --- a/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/properties.json +++ b/webseite/config/subjects/mathe/topics/rechnen-mit-klammern/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise1.pdf b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise1.pdf rename to webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise2.pdf b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise2.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise2.pdf rename to webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise2.pdf diff --git a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise3.pdf b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise3.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise3.pdf rename to webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise3.pdf diff --git a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise4.pdf b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise4.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/schriftliches-dividieren/exercise4.pdf rename to webseite/config/subjects/mathe/topics/schriftliches-dividieren/downloads/exercise4.pdf diff --git a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/properties.json b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/properties.json index 2443f26..c3c5623 100644 --- a/webseite/config/subjects/mathe/topics/schriftliches-dividieren/properties.json +++ b/webseite/config/subjects/mathe/topics/schriftliches-dividieren/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/exercise1.pdf b/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/downloads/exercise1.pdf similarity index 100% rename from webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/exercise1.pdf rename to webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/downloads/exercise1.pdf diff --git a/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/properties.json b/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/properties.json index 04215f4..f09d59a 100644 --- a/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/properties.json +++ b/webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/properties.json @@ -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" ] } \ No newline at end of file diff --git a/webseite/subject.php b/webseite/subject.php index bce9be3..396770f 100644 --- a/webseite/subject.php +++ b/webseite/subject.php @@ -100,7 +100,7 @@ $topics = $subjectData->topics; ?> id/topics/$topicData->id/$fileName") ?>" + href="id/topics/$topicData->id/downloads/$fileName") ?>" target="_blank" download class="download-btn border-2 border-[color); ?>]"> diff --git a/webseite/topic.php b/webseite/topic.php index abc240b..84ded0e 100644 --- a/webseite/topic.php +++ b/webseite/topic.php @@ -99,7 +99,7 @@ if (!isset($topicData)) { foreach ($topicData->files as $fileName) { ?> - From 7c5dfdfa92e61331cb50d3c4668cb5140656afa0 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Sun, 8 Dec 2024 16:02:29 +0100 Subject: [PATCH 04/11] =?UTF-8?q?Hinzuf=C3=BCgen=20von=20downloadbaren=20D?= =?UTF-8?q?ateien=20eingebaut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webseite/classes/TopicData.php | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 2411a71..3adea50 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -179,6 +179,49 @@ class TopicData return true; } + /** + * Lädt eine Datei als Download zum Thema hoch + * @param string $name Dateiname von User + * @param string $tmp_name Temporärer Pfad zur hochgeladenen Datei + * @return bool true, wenn erfolgreich, sonst false + */ + private function addDownload(string $name, string $tmp_name): bool + { + if(!move_uploaded_file($tmp_name, Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "downloads/" . $name)) { + return false; + } + + return true; + } + + /** + * Lädt eine oder mehrere Dateien als Downloads zu diesem Thema hoch + * @param array $files Das array mit den Dateidaten, normalerweise z.B. $_FILES['html-input-name'] + * @return bool true, wenn erfolgreich, sonst false + */ + public function addDownloads(array $files): bool + { + if(count($files) == 0) { + return false; + } + + if(!isset($files["name"]) || !isset($files["tmp_name"])) { + return false; + } + + if(!is_array($files["name"])) { + return $this->addDownload($files["name"], $files["tmp_name"]); + } + + foreach ($files["name"] as $key => $name) { + if(!$this->addDownload($name, $files["tmp_name"][$key])) { + return false; + } + } + + return true; + } + /** * Löscht das Thema inklusive aller zugehörigen Dateien * @return bool true, wenn erfolgreich gelöscht, sonst false From 5ca5899368375113a69a83cd609d6c7c81d46ce7 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Sun, 8 Dec 2024 16:43:51 +0100 Subject: [PATCH 05/11] =?UTF-8?q?L=C3=B6schen=20von=20downloadbaren=20Date?= =?UTF-8?q?ien=20eingebaut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webseite/classes/TopicData.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 3adea50..0396591 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -187,10 +187,20 @@ class TopicData */ private function addDownload(string $name, string $tmp_name): bool { - if(!move_uploaded_file($tmp_name, Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "downloads/" . $name)) { + $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; } @@ -222,6 +232,21 @@ class TopicData return true; } + 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öscht das Thema inklusive aller zugehörigen Dateien * @return bool true, wenn erfolgreich gelöscht, sonst false From ad09024f48bdcf9b94ee731b7fc50d931b558a5d Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Sun, 8 Dec 2024 16:56:35 +0100 Subject: [PATCH 06/11] Upload und Entfernen von Bildern eingebaut --- webseite/classes/TopicData.php | 70 ++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 0396591..9ebc3d8 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -181,11 +181,11 @@ class TopicData /** * Lädt eine Datei als Download zum Thema hoch - * @param string $name Dateiname von User - * @param string $tmp_name Temporärer Pfad zur hochgeladenen Datei + * @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 */ - private function addDownload(string $name, string $tmp_name): bool + public function addDownload(string $name, string $tmp_name): bool { $downloadDirectory = Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "downloads/"; @@ -205,33 +205,10 @@ class TopicData } /** - * Lädt eine oder mehrere Dateien als Downloads zu diesem Thema hoch - * @param array $files Das array mit den Dateidaten, normalerweise z.B. $_FILES['html-input-name'] + * Löscht eine downloadbare Datei des Themas + * @param string $name Dateiname * @return bool true, wenn erfolgreich, sonst false */ - public function addDownloads(array $files): bool - { - if(count($files) == 0) { - return false; - } - - if(!isset($files["name"]) || !isset($files["tmp_name"])) { - return false; - } - - if(!is_array($files["name"])) { - return $this->addDownload($files["name"], $files["tmp_name"]); - } - - foreach ($files["name"] as $key => $name) { - if(!$this->addDownload($name, $files["tmp_name"][$key])) { - return false; - } - } - - return true; - } - public function deleteDownload(string $name): bool { if(!isset($this->files[$name])) { @@ -247,6 +224,43 @@ class TopicData 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()) . ""; + + if(!is_dir($imageDirectory)) { + if(!mkdir($imageDirectory)) { + return false; + } + } + + if(!move_uploaded_file($tmp_name, $imageDirectory . $name)) { + return false; + } + + return true; + } + + /** + * Löscht eine downloadbare Datei 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()) . "$name")) { + return false; + } + + return true; + } + /** * Löscht das Thema inklusive aller zugehörigen Dateien * @return bool true, wenn erfolgreich gelöscht, sonst false From b81068b7014e393e1678c2b6c5e8bde10236eef5 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Sun, 8 Dec 2024 17:07:18 +0100 Subject: [PATCH 07/11] =?UTF-8?q?Bilder=20f=C3=BCr=20Erkl=C3=A4rtexte=20in?= =?UTF-8?q?=20eigene=20Ordner=20ausgelagert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webseite/classes/TopicData.php | 8 ++++---- .../topics/adverbiale-bestimmung/image.png | Bin 5103 -> 0 bytes .../topics/geschichten-erzaehlen/image.png | Bin 5103 -> 0 bytes .../geschichten-erzaehlen/{ => images}/img.png | Bin .../deutsch/topics/personalpronomen/image.png | Bin 5103 -> 0 bytes .../topics/personalpronomen/{ => images}/img.png | Bin .../deutsch/topics/satzglieder/image.png | Bin 5103 -> 0 bytes .../deutsch/topics/vier-faelle/image.png | Bin 5103 -> 0 bytes .../subjects/deutsch/topics/wortarten/image.png | Bin 5103 -> 0 bytes .../topics/wortarten/{ => images}/img.png | Bin .../englisch/topics/LoremIpsum/image.png | Bin 5103 -> 0 bytes .../topics/LoremIpsum/{ => images}/img.png | Bin .../mathe/topics/bruchrechnung/image.png | Bin 5103 -> 0 bytes .../topics/punkt-vor-strichrechnung/image.png | Bin 5103 -> 0 bytes .../mathe/topics/rechnen-mit-einheiten/image.png | Bin 5103 -> 0 bytes .../{ => images}/gewichte.png | Bin .../{ => images}/laengen.png | Bin .../rechnen-mit-einheiten/{ => images}/zeit.png | Bin .../mathe/topics/rechnen-mit-klammern/image.png | Bin 5103 -> 0 bytes .../topics/schriftliches-dividieren/image.png | Bin 5103 -> 0 bytes .../schriftliches-multiplizieren/image.png | Bin 5103 -> 0 bytes 21 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/image.png delete mode 100644 webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/image.png rename webseite/config/subjects/deutsch/topics/geschichten-erzaehlen/{ => images}/img.png (100%) delete mode 100644 webseite/config/subjects/deutsch/topics/personalpronomen/image.png rename webseite/config/subjects/deutsch/topics/personalpronomen/{ => images}/img.png (100%) delete mode 100644 webseite/config/subjects/deutsch/topics/satzglieder/image.png delete mode 100644 webseite/config/subjects/deutsch/topics/vier-faelle/image.png delete mode 100644 webseite/config/subjects/deutsch/topics/wortarten/image.png rename webseite/config/subjects/deutsch/topics/wortarten/{ => images}/img.png (100%) delete mode 100644 webseite/config/subjects/englisch/topics/LoremIpsum/image.png rename webseite/config/subjects/englisch/topics/LoremIpsum/{ => images}/img.png (100%) delete mode 100644 webseite/config/subjects/mathe/topics/bruchrechnung/image.png delete mode 100644 webseite/config/subjects/mathe/topics/punkt-vor-strichrechnung/image.png delete mode 100644 webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/image.png rename webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/{ => images}/gewichte.png (100%) rename webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/{ => images}/laengen.png (100%) rename webseite/config/subjects/mathe/topics/rechnen-mit-einheiten/{ => images}/zeit.png (100%) delete mode 100644 webseite/config/subjects/mathe/topics/rechnen-mit-klammern/image.png delete mode 100644 webseite/config/subjects/mathe/topics/schriftliches-dividieren/image.png delete mode 100644 webseite/config/subjects/mathe/topics/schriftliches-multiplizieren/image.png diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 9ebc3d8..c4bb0c5 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -136,7 +136,7 @@ class TopicData if (!isset($article)) { $article = "Kein Erklärtext vorhanden"; } - $article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId), $article); + $article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article); $result->article = $article; @@ -232,7 +232,7 @@ class TopicData */ public function addImage(string $name, string $tmp_name): bool { - $imageDirectory = Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . ""; + $imageDirectory = Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "images/"; if(!is_dir($imageDirectory)) { if(!mkdir($imageDirectory)) { @@ -248,13 +248,13 @@ class TopicData } /** - * Löscht eine downloadbare Datei des Themas + * 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()) . "$name")) { + if(!unlink(Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "images/$name")) { return false; } diff --git a/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/image.png b/webseite/config/subjects/deutsch/topics/adverbiale-bestimmung/image.png deleted file mode 100644 index a111ca46ece8ed699eb185319416ee738bc1887b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5103 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 Date: Mon, 9 Dec 2024 12:07:17 +0100 Subject: [PATCH 08/11] Konstruktoren zum erstellen von neuen Objekten eingebaut --- webseite/classes/SubjectData.php | 45 +++++++++-------- webseite/classes/TopicData.php | 87 ++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/webseite/classes/SubjectData.php b/webseite/classes/SubjectData.php index 5dc2d7f..487326c 100644 --- a/webseite/classes/SubjectData.php +++ b/webseite/classes/SubjectData.php @@ -40,6 +40,25 @@ 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 Objekt + */ + public function __construct(string $id, string $displayName, string $description, string $color, string $icon, array $topics) + { + $this->id = $id; + $this->displayName = $displayName; + $this->description = $description; + $this->color = $color; + $this->icon = $icon; + $this->topics = $topics; + } + /** * Gibt alle Fächer als SubjectData Objekt zurück * @return array Alle Fächer als SubjectData @@ -77,8 +96,6 @@ class SubjectData */ public static function fromName(string $subjectId): SubjectData|null { - $result = new SubjectData(); - if (Util::containsIllegalCharacters($subjectId)) { return null; } @@ -89,35 +106,23 @@ class SubjectData return null; } - $result->id = $subjectId; - - if (isset($data->displayName)) { - $result->displayName = $data->displayName; - } else { + if (!isset($data->displayName)) { return null; } - if (isset($data->description)) { - $result->description = $data->description; - } else { + if (!isset($data->description)) { return null; } - if (isset($data->color)) { - $result->color = $data->color; - } else { + if (!isset($data->color)) { return null; } - if (isset($data->icon)) { - $result->icon = $data->icon; - } else { + if (!isset($data->icon)) { return null; } - $result->topics = TopicData::getAll($subjectId); - - return $result; + return new SubjectData($subjectId, $data->displayName, $data->description, $data->color, $data->icon, TopicData::getAll($subjectId)); } /** @@ -155,7 +160,7 @@ class SubjectData */ public function delete(): bool { - if(!Util::delete(Config::getSubjectDirectory($this->getId()))) { + if (!Util::delete(Config::getSubjectDirectory($this->getId()))) { return false; } diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index c4bb0c5..b13d48f 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -44,6 +44,29 @@ 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 array $files Die Dateinamen (Datei.pdf) aller downloadbarer Dateien zu diesem Thema 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 + */ + public function __construct(string $id, string $subjectId, string $displayName, string $icon, string $description, array $relatedTopics, array $files, string $article) + { + $this->id = $id; + $this->subjectId = $subjectId; + $this->displayName = $displayName; + $this->icon = $icon; + $this->description = $description; + $this->relatedTopics = $relatedTopics; + $this->files = $files; + $this->article = $article; + } + /** * Gibt alle Themen zu einem gegebenen Fach zurück * @param $subjectId string Die ID des Faches @@ -82,53 +105,45 @@ class TopicData */ public static function fromName(string $subjectId, string $topicId): TopicData|null { - $result = new TopicData(); - - $subjectId = Util::removeIllegalCharacters($subjectId); - $topicId = Util::removeIllegalCharacters($topicId); + if (Util::containsIllegalCharacters($subjectId)) { + return null; + } + if (Util::containsIllegalCharacters($topicId)) { + return null; + } $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; } - if (isset($data->icon)) { - $result->icon = $data->icon; - } else { + if (!isset($data->icon)) { return null; } - if (isset($data->description)) { - $result->description = $data->description; - } else { + if (!isset($data->description)) { return null; } + $relatedTopics = array(); if (isset($data->relatedTopics)) { - $result->relatedTopics = $data->relatedTopics; - } else { - $result->relatedTopics = array(); + $relatedTopics = $data->relatedTopics; } - $result->files = array(); + $files = array(); $downloadDirectory = Config::getTopicDirectory($subjectId, $topicId) . "downloads/"; - if(is_dir($downloadDirectory)) { + if (is_dir($downloadDirectory)) { $fileNames = scandir($downloadDirectory); foreach ($fileNames as $fileName) { if ($fileName == "." || $fileName == "..") { continue; } - $result->files[] = $fileName; + $files[] = $fileName; } } @@ -138,9 +153,7 @@ class TopicData } $article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article); - $result->article = $article; - - return $result; + return new TopicData($topicId, $subjectId, $data->displayName, $data->icon, $data->description, $relatedTopics, $files, $article); } /** @@ -187,15 +200,15 @@ class TopicData */ public function addDownload(string $name, string $tmp_name): bool { - $downloadDirectory = Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "downloads/"; + $downloadDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "downloads/"; - if(!is_dir($downloadDirectory)) { - if(!mkdir($downloadDirectory)) { + if (!is_dir($downloadDirectory)) { + if (!mkdir($downloadDirectory)) { return false; } } - if(!move_uploaded_file($tmp_name, $downloadDirectory . $name)) { + if (!move_uploaded_file($tmp_name, $downloadDirectory . $name)) { return false; } @@ -211,11 +224,11 @@ class TopicData */ public function deleteDownload(string $name): bool { - if(!isset($this->files[$name])) { + if (!isset($this->files[$name])) { return false; } - if(!unlink(Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "downloads/$name")) { + if (!unlink(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "downloads/$name")) { return false; } @@ -232,15 +245,15 @@ class TopicData */ public function addImage(string $name, string $tmp_name): bool { - $imageDirectory = Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "images/"; + $imageDirectory = Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/"; - if(!is_dir($imageDirectory)) { - if(!mkdir($imageDirectory)) { + if (!is_dir($imageDirectory)) { + if (!mkdir($imageDirectory)) { return false; } } - if(!move_uploaded_file($tmp_name, $imageDirectory . $name)) { + if (!move_uploaded_file($tmp_name, $imageDirectory . $name)) { return false; } @@ -254,7 +267,7 @@ class TopicData */ public function deleteImage(string $name): bool { - if(!unlink(Config::getTopicDirectory($this->getSubjectId() , $this->getId()) . "images/$name")) { + if (!unlink(Config::getTopicDirectory($this->getSubjectId(), $this->getId()) . "images/$name")) { return false; } @@ -267,7 +280,7 @@ class TopicData */ public function delete(): bool { - if(!Util::delete(Config::getTopicDirectory($this->getSubjectId(), $this->getId()))) { + if (!Util::delete(Config::getTopicDirectory($this->getSubjectId(), $this->getId()))) { return false; } From 45010810f72e38ae50fb64270044fa16beb09c0b Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Mon, 9 Dec 2024 12:37:47 +0100 Subject: [PATCH 09/11] =?UTF-8?q?Themen=20entfern-=20und=20hinzuf=C3=BCgba?= =?UTF-8?q?r=20gemacht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webseite/classes/SubjectData.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/webseite/classes/SubjectData.php b/webseite/classes/SubjectData.php index 487326c..49fa157 100644 --- a/webseite/classes/SubjectData.php +++ b/webseite/classes/SubjectData.php @@ -167,6 +167,36 @@ class SubjectData 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; From 42132bbbe6a19c806b2971e9a0c526d81ca1ea85 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Tue, 10 Dec 2024 20:34:18 +0100 Subject: [PATCH 10/11] Erstellen von Objekten verbessert --- webseite/classes/SubjectData.php | 64 +++++++++++++++++++++---- webseite/classes/TopicData.php | 82 +++++++++++++++++++++++++++----- 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/webseite/classes/SubjectData.php b/webseite/classes/SubjectData.php index 49fa157..d957308 100644 --- a/webseite/classes/SubjectData.php +++ b/webseite/classes/SubjectData.php @@ -41,22 +41,57 @@ class SubjectData public array $topics; /** - * Erstellt ein neues Fach. Es wird noch nichts gespeichert + * Erstellt ein neues Fach * @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 Objekt + * @param array $topics Alle Themen des Faches als TopicData Object + * @return SubjectData|false Neues Fach oder false, wenn ein Fehler auftritt */ - public function __construct(string $id, string $displayName, string $description, string $color, string $icon, array $topics) + public static function createNew(string $id, string $displayName, string $description, string $color, string $icon, array $topics): SubjectData|false { - $this->id = $id; - $this->displayName = $displayName; - $this->description = $description; - $this->color = $color; - $this->icon = $icon; - $this->topics = $topics; + $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; + + if(!$result->save()) { + return false; + } + + 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): bool + { + if(!is_dir(Config::getSubjectDirectory($subjectId))) { + return false; + } + + return true; } /** @@ -96,9 +131,12 @@ class SubjectData */ 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); @@ -109,20 +147,26 @@ class SubjectData 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; - return new SubjectData($subjectId, $data->displayName, $data->description, $data->color, $data->icon, TopicData::getAll($subjectId)); + $result->topics = TopicData::getAll($subjectId); + + return $result; } /** diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index b13d48f..06cad94 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -1,4 +1,8 @@ id = $id; - $this->subjectId = $subjectId; - $this->displayName = $displayName; - $this->icon = $icon; - $this->description = $description; - $this->relatedTopics = $relatedTopics; - $this->files = $files; - $this->article = $article; + $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 = $files; + + $result->article = $article; + + if(!$result->save()) { + return false; + } + + 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; } /** @@ -105,12 +152,16 @@ class TopicData */ 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)) { @@ -120,19 +171,23 @@ class TopicData 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 = array(); $downloadDirectory = Config::getTopicDirectory($subjectId, $topicId) . "downloads/"; @@ -146,14 +201,15 @@ class TopicData $files[] = $fileName; } } + $result->files = $files; $article = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "article.html"); if (!isset($article)) { $article = "Kein Erklärtext vorhanden"; } - $article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article); + $result->article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article); - return new TopicData($topicId, $subjectId, $data->displayName, $data->icon, $data->description, $relatedTopics, $files, $article); + return $result; } /** From fd1bc5812a58c47b98fb932a097d16fec19a76d3 Mon Sep 17 00:00:00 2001 From: Matthias Grief Date: Tue, 10 Dec 2024 21:16:41 +0100 Subject: [PATCH 11/11] Speichern und Laden von Inhalten verbessert --- webseite/classes/SubjectData.php | 7 +-- webseite/classes/TopicData.php | 90 +++++++++++++++++++++++--------- webseite/classes/Util.php | 18 +++++++ 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/webseite/classes/SubjectData.php b/webseite/classes/SubjectData.php index d957308..b5e2641 100644 --- a/webseite/classes/SubjectData.php +++ b/webseite/classes/SubjectData.php @@ -41,7 +41,7 @@ class SubjectData public array $topics; /** - * Erstellt ein neues Fach + * 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 @@ -72,17 +72,12 @@ class SubjectData $result->topics = $topics; - if(!$result->save()) { - return false; - } - 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): bool diff --git a/webseite/classes/TopicData.php b/webseite/classes/TopicData.php index 06cad94..5ef3e87 100644 --- a/webseite/classes/TopicData.php +++ b/webseite/classes/TopicData.php @@ -49,33 +49,32 @@ class TopicData public string $article; /** - * Erstellt ein neues Thema + * 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 array $files Die Dateinamen (Datei.pdf) aller downloadbarer Dateien zu diesem Thema als String. Die eigentlichen Dateien müssen seperat hinzugefügt werden * @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, array $files, string $article): TopicData|false + 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)) { + if (Util::containsIllegalCharacters($subjectId)) { return false; } - if(!SubjectData::exists($subjectId)) { + if (!SubjectData::exists($subjectId)) { return false; } $result->subjectId = $subjectId; - if(Util::containsIllegalCharacters($id)) { + if (Util::containsIllegalCharacters($id)) { return false; } - if(self::exists($subjectId, $id)) { + if (self::exists($subjectId, $id)) { return false; } $result->id = $id; @@ -88,14 +87,10 @@ class TopicData $result->relatedTopics = $relatedTopics; - $result->files = $files; + $result->files = array(); $result->article = $article; - if(!$result->save()) { - return false; - } - return $result; } @@ -107,7 +102,7 @@ class TopicData */ public static function exists(string $subjectId, string $topicId): bool { - if(!is_dir(Config::getTopicDirectory($subjectId, $topicId))) { + if (!is_dir(Config::getTopicDirectory($subjectId, $topicId))) { return false; } @@ -189,18 +184,7 @@ class TopicData } $result->relatedTopics = $relatedTopics; - $files = array(); - $downloadDirectory = Config::getTopicDirectory($subjectId, $topicId) . "downloads/"; - if (is_dir($downloadDirectory)) { - $fileNames = scandir($downloadDirectory); - foreach ($fileNames as $fileName) { - if ($fileName == "." || $fileName == "..") { - continue; - } - - $files[] = $fileName; - } - } + $files = Util::getFilesFromDirectory(Config::getTopicDirectory($subjectId, $topicId) . "downloads/"); $result->files = $files; $article = Util::readFileContent(Config::getTopicDirectory($subjectId, $topicId) . "article.html"); @@ -209,6 +193,9 @@ class TopicData } $result->article = str_replace('$TOPICPATH', Config::getTopicDirectory($subjectId, $topicId) . "images", $article); + $result->cleanupRelatedTopics(); + $result->cleanupFiles(); + return $result; } @@ -218,6 +205,9 @@ class TopicData */ public function save(): bool { + $this->cleanupRelatedTopics(); + $this->cleanupFiles(); + $data = array(); $data["displayName"] = $this->displayName; $data["icon"] = $this->icon; @@ -316,6 +306,56 @@ class TopicData 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) === false) { + Util::delete(Config::getTopicDirectory($this->subjectId, $this->id) . "downloads/$file"); + $changed = true; + } + } + + return $changed; + } + /** * Löscht ein Bild des Themas * @param string $name Dateiname diff --git a/webseite/classes/Util.php b/webseite/classes/Util.php index a8cabb8..2802e8a 100644 --- a/webseite/classes/Util.php +++ b/webseite/classes/Util.php @@ -29,6 +29,24 @@ class Util 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