539 lines
23 KiB
Java
539 lines
23 KiB
Java
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;
|
||
import org.bukkit.configuration.file.YamlConfiguration;
|
||
|
||
import java.io.File;
|
||
import java.io.IOException;
|
||
import java.util.*;
|
||
|
||
/**
|
||
* Verwaltet FAQ-Einträge und FAQ-Kategorien.
|
||
*
|
||
* Speichermodus wird in config.yml festgelegt:
|
||
*
|
||
* faq-storage: file → faqs.yml (Standard, Einzelserver)
|
||
* faq-storage: mysql → MySQL-Datenbank (BungeeCord / Multi-Server, use-mysql muss true sein)
|
||
*
|
||
* Im MySQL-Modus werden Tabellen faq_entries und faq_categories verwendet.
|
||
* Im Datei-Modus verhält sich der Manager exakt wie bisher (faqs.yml).
|
||
*
|
||
* 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 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();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 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();
|
||
faqFile.createNewFile();
|
||
} catch (IOException e) {
|
||
plugin.getLogger().severe("[FaqManager] Konnte faqs.yml nicht erstellen: " + e.getMessage());
|
||
}
|
||
faqConfig = new YamlConfiguration();
|
||
loadDefaults();
|
||
saveToFile();
|
||
return;
|
||
}
|
||
|
||
faqConfig = YamlConfiguration.loadConfiguration(faqFile);
|
||
|
||
ConfigurationSection catSection = faqConfig.getConfigurationSection("categories");
|
||
if (catSection != null && !catSection.getKeys(false).isEmpty()) {
|
||
for (String key : catSection.getKeys(false)) {
|
||
ConfigurationSection cat = catSection.getConfigurationSection(key);
|
||
if (cat == null) continue;
|
||
String name = cat.getString("name", capitalize(key));
|
||
String color = cat.getString("color", "&7");
|
||
String desc = cat.getString("description", "");
|
||
categories.put(key.toLowerCase(), new FaqCategory(key, name, color, desc));
|
||
}
|
||
}
|
||
|
||
ConfigurationSection faqSection = faqConfig.getConfigurationSection("faqs");
|
||
if (faqSection != null) {
|
||
for (String key : faqSection.getKeys(false)) {
|
||
try {
|
||
int id = Integer.parseInt(key);
|
||
String question = faqConfig.getString("faqs." + key + ".question", "");
|
||
String answer = faqConfig.getString("faqs." + key + ".answer", "");
|
||
String category = faqConfig.getString("faqs." + key + ".category", null);
|
||
if (!question.isBlank() && !answer.isBlank()) {
|
||
FaqEntry entry = new FaqEntry(id, question, answer);
|
||
entry.setCategoryKey(normalizeCategoryKey(category));
|
||
entries.add(entry);
|
||
if (id >= nextId) nextId = id + 1;
|
||
}
|
||
} catch (NumberFormatException ignored) {}
|
||
}
|
||
}
|
||
|
||
entries.sort(Comparator.comparingInt(FaqEntry::getId));
|
||
|
||
if (plugin.isDebug()) {
|
||
plugin.getLogger().info("[FaqManager] Datei: " + categories.size()
|
||
+ " Kategorie(n), " + entries.size() + " FAQ(s) geladen.");
|
||
}
|
||
}
|
||
|
||
private void loadDefaults() {
|
||
faqConfig.options().header(
|
||
"FAQ-System – faqs.yml\n" +
|
||
"Wird automatisch generiert. Kategorien sind optional.\n" +
|
||
"Material/Textur der Kategorie-Items: config.yml → gui-settings.faq.category-head-item"
|
||
);
|
||
|
||
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"));
|
||
|
||
writeFileEntry(1, "Wie erstelle ich ein Ticket?",
|
||
"Nutze den Befehl /ticket create [Kategorie] [Prio] <Beschreibung>.", "tickets");
|
||
writeFileEntry(2, "Wie lange dauert die Bearbeitung?",
|
||
"Unser Support-Team bearbeitet Tickets so schnell wie möglich.", "tickets");
|
||
writeFileEntry(3, "Kann ich mein Ticket löschen?",
|
||
"Ja! Öffne /ticket list und klicke auf dein Ticket.", "tickets");
|
||
writeFileEntry(4, "Wie kann ich meinen Support bewerten?",
|
||
"Mit /ticket rate <ID> good/bad nach dem Schließen.", "tickets");
|
||
nextId = 5;
|
||
|
||
for (int i = 1; i <= 4; i++) {
|
||
String q = faqConfig.getString("faqs." + i + ".question", "");
|
||
String a = faqConfig.getString("faqs." + i + ".answer", "");
|
||
FaqEntry e = new FaqEntry(i, q, a);
|
||
e.setCategoryKey("tickets");
|
||
entries.add(e);
|
||
}
|
||
}
|
||
|
||
// ── YAML-Hilfsmethoden ─────────────────────────────────────────────────
|
||
|
||
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 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)) {
|
||
faqConfig.set("faqs." + id + ".category", categoryKey);
|
||
}
|
||
}
|
||
|
||
private void saveToFile() {
|
||
if (faqConfig == null) return;
|
||
try {
|
||
faqConfig.save(faqFile);
|
||
} catch (IOException e) {
|
||
plugin.getLogger().severe("[FaqManager] Konnte faqs.yml nicht speichern: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// PUBLIC API – KATEGORIEN
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
|
||
public boolean hasCategoriesEnabled() { return !categories.isEmpty(); }
|
||
|
||
public List<FaqCategory> getAllCategories() {
|
||
return Collections.unmodifiableList(new ArrayList<>(categories.values()));
|
||
}
|
||
|
||
public FaqCategory getCategoryByKey(String key) {
|
||
if (key == null) return null;
|
||
return categories.get(key.toLowerCase());
|
||
}
|
||
|
||
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);
|
||
|
||
if (useMysql) {
|
||
plugin.getDatabaseManager().createFaqCategory(lowerKey, name, color, description);
|
||
} else {
|
||
writeFileCategory(lowerKey, name, color, description);
|
||
saveToFile();
|
||
}
|
||
return cat;
|
||
}
|
||
|
||
public boolean editCategory(String key, String name, String color, String description) {
|
||
String lowerKey = key.toLowerCase();
|
||
if (!categories.containsKey(lowerKey)) return false;
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
public boolean deleteCategory(String key) {
|
||
String lowerKey = key.toLowerCase();
|
||
if (!categories.containsKey(lowerKey)) return false;
|
||
|
||
categories.remove(lowerKey);
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
public List<FaqEntry> getByCategory(String categoryKey) {
|
||
String normalizedKey = normalizeCategoryKey(categoryKey);
|
||
List<FaqEntry> result = new ArrayList<>();
|
||
for (FaqEntry e : entries) {
|
||
if (normalizedKey.equals(e.getCategoryKey())) result.add(e);
|
||
}
|
||
return Collections.unmodifiableList(result);
|
||
}
|
||
|
||
public int countByCategory(String categoryKey) {
|
||
return getByCategory(categoryKey).size();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// PUBLIC API – EINTRÄGE
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
|
||
public List<FaqEntry> getAll() { return Collections.unmodifiableList(entries); }
|
||
|
||
public FaqEntry getById(int id) {
|
||
return entries.stream().filter(e -> e.getId() == id).findFirst().orElse(null);
|
||
}
|
||
|
||
public FaqEntry add(String question, String answer, String categoryKey) {
|
||
String normalizedKey = normalizeCategoryKey(categoryKey);
|
||
|
||
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) {
|
||
return add(question, answer, null);
|
||
}
|
||
|
||
public boolean edit(int id, String question, String answer) {
|
||
FaqEntry entry = getById(id);
|
||
if (entry == null) return false;
|
||
|
||
entry.setQuestion(question);
|
||
entry.setAnswer(answer);
|
||
|
||
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 (useMysql) {
|
||
return plugin.getDatabaseManager().updateFaqEntryCategory(id, normalizedKey);
|
||
} else {
|
||
if (normalizedKey.equals(UNCATEGORIZED_KEY)) {
|
||
faqConfig.set("faqs." + id + ".category", null);
|
||
} else {
|
||
faqConfig.set("faqs." + id + ".category", normalizedKey);
|
||
}
|
||
saveToFile();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
public boolean delete(int id) {
|
||
FaqEntry entry = getById(id);
|
||
if (entry == null) return false;
|
||
|
||
entries.remove(entry);
|
||
|
||
if (useMysql) {
|
||
return plugin.getDatabaseManager().deleteFaqEntry(id);
|
||
} else {
|
||
faqConfig.set("faqs." + id, null);
|
||
saveToFile();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
public void reload() { load(); }
|
||
|
||
/** 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;
|
||
String lower = key.toLowerCase();
|
||
if (lower.equals(UNCATEGORIZED_KEY)) return UNCATEGORIZED_KEY;
|
||
if (!categories.isEmpty() && !categories.containsKey(lower)) return UNCATEGORIZED_KEY;
|
||
return lower;
|
||
}
|
||
|
||
private static String capitalize(String s) {
|
||
if (s == null || s.isEmpty()) return s;
|
||
return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
|
||
}
|
||
} |