Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-16 11:48:01 +02:00
parent 5e102ec4e1
commit 084172116d
13 changed files with 1377 additions and 142 deletions

View File

@@ -1,6 +1,7 @@
package de.ticketsystem.manager;
import de.ticketsystem.TicketPlugin;
import de.ticketsystem.database.DatabaseManager;
import de.ticketsystem.model.FaqCategory;
import de.ticketsystem.model.FaqEntry;
import org.bukkit.configuration.ConfigurationSection;
@@ -11,54 +12,93 @@ import java.io.IOException;
import java.util.*;
/**
* Manages FAQ entries and FAQ categories stored in faqs.yml.
* Verwaltet FAQ-Einträge und FAQ-Kategorien.
*
* faqs.yml wird beim ersten Start automatisch mit Beispiel-Kategorien und -FAQs generiert.
* Admins können Kategorien und FAQs direkt in-game verwalten (GUI + Befehle).
* Speichermodus wird in config.yml festgelegt:
*
* faqs.yml Layout:
* faq-storage: file → faqs.yml (Standard, Einzelserver)
* faq-storage: mysql → MySQL-Datenbank (BungeeCord / Multi-Server, use-mysql muss true sein)
*
* categories:
* tickets:
* name: "Tickets"
* color: "&b"
* description: "Fragen zum Ticket-System"
* Im MySQL-Modus werden Tabellen faq_entries und faq_categories verwendet.
* Im Datei-Modus verhält sich der Manager exakt wie bisher (faqs.yml).
*
* faqs:
* 1:
* question: "Wie erstelle ich ein Ticket?"
* answer: "Nutze /ticket create ..."
* category: "tickets"
*
* Material und Textur der Kategorie-Items werden NICHT hier gespeichert
* sie werden zentral in config.yml unter gui-settings.faq.category-head-item gesteuert.
* Die öffentliche API (add, edit, delete, getAll …) ist in beiden Modi identisch
* FaqGUI, FaqHandler und ApiHandler müssen nicht angepasst werden.
*/
public class FaqManager {
public static final String UNCATEGORIZED_KEY = "__none__";
private final TicketPlugin plugin;
private final File faqFile;
private YamlConfiguration faqConfig;
private final boolean useMysql;
// ── YAML-Modus ────────────────────────────────────────────────────────
private final File faqFile;
private YamlConfiguration faqConfig;
// ── In-Memory-State (beide Modi) ──────────────────────────────────────
private final List<FaqEntry> entries = new ArrayList<>();
private final LinkedHashMap<String, FaqCategory> categories = new LinkedHashMap<>();
/** Nur im YAML-Modus relevant im MySQL-Modus liefert AUTO_INCREMENT die ID */
private int nextId = 1;
public FaqManager(TicketPlugin plugin) {
this.plugin = plugin;
this.faqFile = new File(plugin.getDataFolder(), "faqs.yml");
// Speichermodus direkt aus use-mysql lesen kein separater faq-storage Key nötig
this.useMysql = plugin.getConfig().getBoolean("use-mysql", false);
if (useMysql) {
plugin.getLogger().info("[FaqManager] Speichermodus: MySQL (faq_entries / faq_categories)");
} else {
plugin.getLogger().info("[FaqManager] Speichermodus: Datei (faqs.yml)");
}
load();
}
// ─────────────────────────── Loading & Saving ───────────────────────────
// ═══════════════════════════════════════════════════════════════════════
// LADEN & SPEICHERN
// ═══════════════════════════════════════════════════════════════════════
private void load() {
entries.clear();
categories.clear();
nextId = 1;
if (useMysql) {
loadFromMySQL();
} else {
loadFromFile();
}
}
// ── MySQL-Modus ────────────────────────────────────────────────────────
private void loadFromMySQL() {
DatabaseManager db = plugin.getDatabaseManager();
for (FaqCategory cat : db.getFaqCategories()) {
categories.put(cat.getKey(), cat);
}
for (FaqEntry e : db.getFaqEntries()) {
e.setCategoryKey(normalizeCategoryKey(e.getCategoryKey()));
entries.add(e);
if (e.getId() >= nextId) nextId = e.getId() + 1;
}
if (plugin.isDebug()) {
plugin.getLogger().info("[FaqManager] MySQL: " + categories.size()
+ " Kategorie(n), " + entries.size() + " FAQ(s) geladen.");
}
}
// ── Datei-Modus ────────────────────────────────────────────────────────
private void loadFromFile() {
if (!faqFile.exists()) {
try {
faqFile.getParentFile().mkdirs();
@@ -68,13 +108,12 @@ public class FaqManager {
}
faqConfig = new YamlConfiguration();
loadDefaults();
save();
saveToFile();
return;
}
faqConfig = YamlConfiguration.loadConfiguration(faqFile);
// ── 1. Kategorien laden ───────────────────────────────────────────
ConfigurationSection catSection = faqConfig.getConfigurationSection("categories");
if (catSection != null && !catSection.getKeys(false).isEmpty()) {
for (String key : catSection.getKeys(false)) {
@@ -85,13 +124,8 @@ public class FaqManager {
String desc = cat.getString("description", "");
categories.put(key.toLowerCase(), new FaqCategory(key, name, color, desc));
}
if (plugin.isDebug()) {
plugin.getLogger().info("[FaqManager] " + categories.size()
+ " FAQ-Kategorie(n) geladen: " + String.join(", ", categories.keySet()));
}
}
// ── 2. FAQ-Einträge laden ─────────────────────────────────────────
ConfigurationSection faqSection = faqConfig.getConfigurationSection("faqs");
if (faqSection != null) {
for (String key : faqSection.getKeys(false)) {
@@ -113,7 +147,8 @@ public class FaqManager {
entries.sort(Comparator.comparingInt(FaqEntry::getId));
if (plugin.isDebug()) {
plugin.getLogger().info("[FaqManager] " + entries.size() + " FAQ(s) geladen.");
plugin.getLogger().info("[FaqManager] Datei: " + categories.size()
+ " Kategorie(n), " + entries.size() + " FAQ(s) geladen.");
}
}
@@ -124,23 +159,23 @@ public class FaqManager {
"Material/Textur der Kategorie-Items: config.yml → gui-settings.faq.category-head-item"
);
writeCategory("general", "Allgemein", "&e", "Allgemeine Fragen zum Server");
writeCategory("rules", "Regeln", "&c", "Fragen zu den Server-Regeln");
writeCategory("gameplay", "Gameplay", "&a", "Fragen zum Spielgeschehen");
writeCategory("tickets", "Tickets", "&b", "Fragen zum Ticket-System");
writeFileCategory("general", "Allgemein", "&e", "Allgemeine Fragen zum Server");
writeFileCategory("rules", "Regeln", "&c", "Fragen zu den Server-Regeln");
writeFileCategory("gameplay", "Gameplay", "&a", "Fragen zum Spielgeschehen");
writeFileCategory("tickets", "Tickets", "&b", "Fragen zum Ticket-System");
categories.put("general", new FaqCategory("general", "Allgemein", "&e", "Allgemeine Fragen zum Server"));
categories.put("rules", new FaqCategory("rules", "Regeln", "&c", "Fragen zu den Server-Regeln"));
categories.put("gameplay", new FaqCategory("gameplay", "Gameplay", "&a", "Fragen zum Spielgeschehen"));
categories.put("tickets", new FaqCategory("tickets", "Tickets", "&b", "Fragen zum Ticket-System"));
writeEntry(1, "Wie erstelle ich ein Ticket?",
writeFileEntry(1, "Wie erstelle ich ein Ticket?",
"Nutze den Befehl /ticket create [Kategorie] [Prio] <Beschreibung>.", "tickets");
writeEntry(2, "Wie lange dauert die Bearbeitung?",
writeFileEntry(2, "Wie lange dauert die Bearbeitung?",
"Unser Support-Team bearbeitet Tickets so schnell wie möglich.", "tickets");
writeEntry(3, "Kann ich mein Ticket löschen?",
writeFileEntry(3, "Kann ich mein Ticket löschen?",
"Ja! Öffne /ticket list und klicke auf dein Ticket.", "tickets");
writeEntry(4, "Wie kann ich meinen Support bewerten?",
writeFileEntry(4, "Wie kann ich meinen Support bewerten?",
"Mit /ticket rate <ID> good/bad nach dem Schließen.", "tickets");
nextId = 5;
@@ -155,13 +190,13 @@ public class FaqManager {
// ── YAML-Hilfsmethoden ─────────────────────────────────────────────────
private void writeCategory(String key, String name, String color, String description) {
private void writeFileCategory(String key, String name, String color, String description) {
faqConfig.set("categories." + key + ".name", name);
faqConfig.set("categories." + key + ".color", color);
faqConfig.set("categories." + key + ".description", description);
}
private void writeEntry(int id, String question, String answer, String categoryKey) {
private void writeFileEntry(int id, String question, String answer, String categoryKey) {
faqConfig.set("faqs." + id + ".question", question);
faqConfig.set("faqs." + id + ".answer", answer);
if (categoryKey != null && !categoryKey.equals(UNCATEGORIZED_KEY)) {
@@ -169,7 +204,8 @@ public class FaqManager {
}
}
private void save() {
private void saveToFile() {
if (faqConfig == null) return;
try {
faqConfig.save(faqFile);
} catch (IOException e) {
@@ -177,7 +213,9 @@ public class FaqManager {
}
}
// ─────────────────────────── Public API Kategorien ───────────────────
// ═══════════════════════════════════════════════════════════════════════
// PUBLIC API KATEGORIEN
// ═══════════════════════════════════════════════════════════════════════
public boolean hasCategoriesEnabled() { return !categories.isEmpty(); }
@@ -190,55 +228,61 @@ public class FaqManager {
return categories.get(key.toLowerCase());
}
/**
* Fügt eine neue Kategorie hinzu und speichert sofort.
*
* @return null wenn der Schlüssel bereits existiert, sonst die neue FaqCategory.
*/
public FaqCategory addCategory(String key, String name, String color, String description) {
String lowerKey = key.toLowerCase().replaceAll("\\s+", "_");
if (categories.containsKey(lowerKey)) return null;
FaqCategory cat = new FaqCategory(lowerKey, name, color, description);
categories.put(lowerKey, cat);
writeCategory(lowerKey, name, color, description);
save();
if (useMysql) {
plugin.getDatabaseManager().createFaqCategory(lowerKey, name, color, description);
} else {
writeFileCategory(lowerKey, name, color, description);
saveToFile();
}
return cat;
}
/**
* Bearbeitet eine bestehende Kategorie und speichert sofort.
*
* @return true wenn gefunden und aktualisiert.
*/
public boolean editCategory(String key, String name, String color, String description) {
String lowerKey = key.toLowerCase();
if (!categories.containsKey(lowerKey)) return false;
FaqCategory updated = new FaqCategory(lowerKey, name, color, description);
categories.put(lowerKey, updated);
writeCategory(lowerKey, name, color, description);
save();
return true;
categories.put(lowerKey, new FaqCategory(lowerKey, name, color, description));
if (useMysql) {
return plugin.getDatabaseManager().updateFaqCategory(lowerKey, name, color, description);
} else {
writeFileCategory(lowerKey, name, color, description);
saveToFile();
return true;
}
}
/**
* Löscht eine Kategorie. FAQs dieser Kategorie werden auf UNCATEGORIZED_KEY gesetzt.
*
* @return true wenn gefunden und gelöscht.
*/
public boolean deleteCategory(String key) {
String lowerKey = key.toLowerCase();
if (!categories.containsKey(lowerKey)) return false;
categories.remove(lowerKey);
faqConfig.set("categories." + lowerKey, null);
// FAQs dieser Kategorie auf "keine Kategorie" setzen
for (FaqEntry entry : entries) {
if (lowerKey.equals(entry.getCategoryKey())) {
entry.setCategoryKey(UNCATEGORIZED_KEY);
faqConfig.set("faqs." + entry.getId() + ".category", null);
if (useMysql) {
boolean ok = plugin.getDatabaseManager().deleteFaqCategory(lowerKey);
// In-Memory synchronisieren
entries.stream()
.filter(e -> lowerKey.equals(e.getCategoryKey()))
.forEach(e -> e.setCategoryKey(UNCATEGORIZED_KEY));
return ok;
} else {
faqConfig.set("categories." + lowerKey, null);
for (FaqEntry entry : entries) {
if (lowerKey.equals(entry.getCategoryKey())) {
entry.setCategoryKey(UNCATEGORIZED_KEY);
faqConfig.set("faqs." + entry.getId() + ".category", null);
}
}
saveToFile();
return true;
}
save();
return true;
}
public List<FaqEntry> getByCategory(String categoryKey) {
@@ -254,7 +298,9 @@ public class FaqManager {
return getByCategory(categoryKey).size();
}
// ─────────────────────────── Public API Einträge ─────────────────────
// ═══════════════════════════════════════════════════════════════════════
// PUBLIC API EINTRÄGE
// ═══════════════════════════════════════════════════════════════════════
public List<FaqEntry> getAll() { return Collections.unmodifiableList(entries); }
@@ -263,14 +309,24 @@ public class FaqManager {
}
public FaqEntry add(String question, String answer, String categoryKey) {
int id = nextId++;
String normalizedKey = normalizeCategoryKey(categoryKey);
FaqEntry entry = new FaqEntry(id, question, answer);
entry.setCategoryKey(normalizedKey);
entries.add(entry);
writeEntry(id, question, answer, normalizedKey);
save();
return entry;
if (useMysql) {
int id = plugin.getDatabaseManager().createFaqEntry(question, answer, normalizedKey);
if (id == -1) return null;
FaqEntry entry = new FaqEntry(id, question, answer);
entry.setCategoryKey(normalizedKey);
entries.add(entry);
return entry;
} else {
int id = nextId++;
FaqEntry entry = new FaqEntry(id, question, answer);
entry.setCategoryKey(normalizedKey);
entries.add(entry);
writeFileEntry(id, question, answer, normalizedKey);
saveToFile();
return entry;
}
}
public FaqEntry add(String question, String answer) {
@@ -280,39 +336,193 @@ public class FaqManager {
public boolean edit(int id, String question, String answer) {
FaqEntry entry = getById(id);
if (entry == null) return false;
entry.setQuestion(question);
entry.setAnswer(answer);
writeEntry(id, question, answer, entry.getCategoryKey());
save();
return true;
if (useMysql) {
return plugin.getDatabaseManager().updateFaqEntry(id, question, answer);
} else {
writeFileEntry(id, question, answer, entry.getCategoryKey());
saveToFile();
return true;
}
}
public boolean setCategory(int id, String categoryKey) {
FaqEntry entry = getById(id);
if (entry == null) return false;
String normalizedKey = normalizeCategoryKey(categoryKey);
entry.setCategoryKey(normalizedKey);
if (normalizedKey.equals(UNCATEGORIZED_KEY)) {
faqConfig.set("faqs." + id + ".category", null);
if (useMysql) {
return plugin.getDatabaseManager().updateFaqEntryCategory(id, normalizedKey);
} else {
faqConfig.set("faqs." + id + ".category", normalizedKey);
if (normalizedKey.equals(UNCATEGORIZED_KEY)) {
faqConfig.set("faqs." + id + ".category", null);
} else {
faqConfig.set("faqs." + id + ".category", normalizedKey);
}
saveToFile();
return true;
}
save();
return true;
}
public boolean delete(int id) {
FaqEntry entry = getById(id);
if (entry == null) return false;
entries.remove(entry);
faqConfig.set("faqs." + id, null);
save();
return true;
if (useMysql) {
return plugin.getDatabaseManager().deleteFaqEntry(id);
} else {
faqConfig.set("faqs." + id, null);
saveToFile();
return true;
}
}
public void reload() { load(); }
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
/** Gibt zurück ob MySQL als FAQ-Speicher aktiv ist. */
public boolean isUsingMySQL() { return useMysql; }
/**
* Migriert FAQ-Daten aus faqs.yml in die MySQL-Datenbank.
*
* Nur ausführbar wenn use-mysql: true aktiv ist.
* Nach erfolgreichem Import wird der In-Memory-Cache neu geladen.
*
* @return int[2] { importierte Kategorien, importierte Einträge } oder null bei Fehler
*/
public int[] migrateFaqToMySQL() {
if (!useMysql) {
plugin.getLogger().warning("[FaqManager] Migration nur im MySQL-Modus möglich (use-mysql: true).");
return null;
}
if (!faqFile.exists()) {
plugin.getLogger().warning("[FaqManager] faqs.yml nicht gefunden Migration abgebrochen.");
return null;
}
try {
YamlConfiguration yml = YamlConfiguration.loadConfiguration(faqFile);
DatabaseManager db = plugin.getDatabaseManager();
// Bestehende DB-Einträge als Duplikat-Schutz laden
java.util.Set<String> existingKeys = new java.util.HashSet<>();
for (FaqEntry e : db.getFaqEntries()) {
existingKeys.add(e.getQuestion().trim().toLowerCase()
+ "\u0000" + e.getAnswer().trim().toLowerCase());
}
// Kategorien importieren
int importedCats = 0;
ConfigurationSection catSec = yml.getConfigurationSection("categories");
if (catSec != null) {
for (String key : catSec.getKeys(false)) {
String name = yml.getString("categories." + key + ".name", key);
String color = yml.getString("categories." + key + ".color", "&7");
String desc = yml.getString("categories." + key + ".description", "");
if (db.createFaqCategory(key.toLowerCase(), name, color, desc)) importedCats++;
}
}
// Einträge importieren — Duplikat-Prüfung per ID (nicht nur Inhalt)
int importedEntries = 0;
java.util.Set<Integer> existingIds = new java.util.HashSet<>();
for (FaqEntry e : db.getFaqEntries()) existingIds.add(e.getId());
ConfigurationSection faqSec = yml.getConfigurationSection("faqs");
if (faqSec != null) {
for (String key : faqSec.getKeys(false)) {
String question = yml.getString("faqs." + key + ".question", "");
String answer = yml.getString("faqs." + key + ".answer", "");
String category = yml.getString("faqs." + key + ".category", null);
if (question.isBlank() || answer.isBlank()) continue;
// Duplikat-Check: gleiche Frage UND Antwort (normalisiert)
String dupKey = question.trim().toLowerCase() + "\u0000" + answer.trim().toLowerCase();
if (existingKeys.contains(dupKey)) continue;
int id = db.createFaqEntry(question, answer,
category != null ? category : UNCATEGORIZED_KEY);
if (id != -1) { importedEntries++; existingKeys.add(dupKey); }
}
}
plugin.getLogger().info("[FaqManager] Migration: " + importedCats
+ " Kategorie(n), " + importedEntries + " Eintrag/Einträge importiert.");
load();
return new int[]{importedCats, importedEntries};
} catch (Exception e) {
plugin.getLogger().severe("[FaqManager] Fehler bei Migration: " + e.getMessage());
return null;
}
}
/**
* Exportiert alle FAQ-Daten aus MySQL zurück in die faqs.yml.
* Nützlich beim Wechsel von MySQL zurück auf Datei-Modus.
* Überschreibt eine bestehende faqs.yml nach Sicherheits-Backup.
*
* @return int[2] { exportierte Kategorien, exportierte Einträge } oder null bei Fehler
*/
public int[] migrateFaqToFile() {
if (!useMysql) {
plugin.getLogger().warning("[FaqManager] Rückmigration nur im MySQL-Modus möglich.");
return null;
}
try {
DatabaseManager db = plugin.getDatabaseManager();
// Backup der bestehenden faqs.yml
if (faqFile.exists()) {
String ts = new java.text.SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")
.format(new java.util.Date());
java.io.File backup = new java.io.File(faqFile.getParent(), "faqs_backup_" + ts + ".yml");
java.nio.file.Files.copy(faqFile.toPath(), backup.toPath(),
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
plugin.getLogger().info("[FaqManager] Backup erstellt: " + backup.getName());
}
YamlConfiguration out = new YamlConfiguration();
// Kategorien schreiben
int exportedCats = 0;
for (FaqCategory cat : db.getFaqCategories()) {
out.set("categories." + cat.getKey() + ".name", cat.getName());
out.set("categories." + cat.getKey() + ".color", cat.getColor());
out.set("categories." + cat.getKey() + ".description", cat.getDescription());
exportedCats++;
}
// Einträge schreiben
int exportedEntries = 0;
for (FaqEntry e : db.getFaqEntries()) {
String base = "faqs." + e.getId();
out.set(base + ".question", e.getQuestion());
out.set(base + ".answer", e.getAnswer());
if (e.hasCategory()) out.set(base + ".category", e.getCategoryKey());
exportedEntries++;
}
out.save(faqFile);
plugin.getLogger().info("[FaqManager] Rückmigration: " + exportedCats
+ " Kategorie(n), " + exportedEntries + " Eintrag/Einträge in faqs.yml gespeichert.");
return new int[]{exportedCats, exportedEntries};
} catch (Exception e) {
plugin.getLogger().severe("[FaqManager] Fehler bei Rückmigration: " + e.getMessage());
return null;
}
}
// ═══════════════════════════════════════════════════════════════════════
// HILFSMETHODEN
// ═══════════════════════════════════════════════════════════════════════
private String normalizeCategoryKey(String key) {
if (key == null || key.isBlank()) return UNCATEGORIZED_KEY;