Files
TicketSystem/src/main/java/de/ticketsystem/manager/FaqManager.java
2026-04-16 11:48:01 +02:00

539 lines
23 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}