Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-19 17:43:08 +02:00
parent 3dfbd33656
commit 179d46bb6f
7 changed files with 455 additions and 108 deletions

View File

@@ -115,11 +115,18 @@ public class TicketPlugin extends JavaPlugin {
// Manager, GUI, FAQ & Discord-Webhook initialisieren // Manager, GUI, FAQ & Discord-Webhook initialisieren
categoryManager = new CategoryManager(this); categoryManager = new CategoryManager(this);
ticketManager = new TicketManager(this); ticketManager = new TicketManager(this);
faqManager = new FaqManager(this);
ticketGUI = new TicketGUI(this); ticketGUI = new TicketGUI(this);
faqGUI = new FaqGUI(this);
discordWebhook = new DiscordWebhook(this); discordWebhook = new DiscordWebhook(this);
// ── FAQ-System (nur initialisieren wenn aktiviert) ─────────────────
if (isFaqEnabled()) {
faqManager = new FaqManager(this);
faqGUI = new FaqGUI(this);
getLogger().info("[FAQ] FAQ-System aktiviert.");
} else {
getLogger().info("[FAQ] FAQ-System deaktiviert (faq-enabled: false in config.yml).");
}
if (getConfig().getBoolean("discord.enabled", false)) { if (getConfig().getBoolean("discord.enabled", false)) {
String url = getConfig().getString("discord.webhook-url", ""); String url = getConfig().getString("discord.webhook-url", "");
if (url.isEmpty()) { if (url.isEmpty()) {
@@ -134,7 +141,9 @@ public class TicketPlugin extends JavaPlugin {
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this); getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
getServer().getPluginManager().registerEvents(ticketGUI, this); getServer().getPluginManager().registerEvents(ticketGUI, this);
if (faqGUI != null) {
getServer().getPluginManager().registerEvents(faqGUI, this); getServer().getPluginManager().registerEvents(faqGUI, this);
}
// Automatische Archivierung // Automatische Archivierung
int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24); int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24);
@@ -166,6 +175,9 @@ public class TicketPlugin extends JavaPlugin {
if (webServer != null) webServer.stop(); if (webServer != null) webServer.stop();
if (ticketCache != null) ticketCache.clear(); if (ticketCache != null) ticketCache.clear();
if (databaseManager != null) databaseManager.disconnect(); if (databaseManager != null) databaseManager.disconnect();
// Adventure BukkitAudiences sauber schließen
if (languageManager != null && languageManager.getAudiences() != null)
languageManager.getAudiences().close();
getLogger().info("TicketSystem wurde deaktiviert."); getLogger().info("TicketSystem wurde deaktiviert.");
} }
@@ -322,6 +334,14 @@ public class TicketPlugin extends JavaPlugin {
TicketPriority.reloadLocalizedNames(this); TicketPriority.reloadLocalizedNames(this);
} }
/**
* Gibt zurück ob das FAQ-System aktiviert ist.
* Konfigurierbar in config.yml → faq-enabled (Standard: true)
*/
public boolean isFaqEnabled() {
return getConfig().getBoolean("faq-enabled", true);
}
public static TicketPlugin getInstance() { return instance; } public static TicketPlugin getInstance() { return instance; }
public LanguageManager getLanguageManager() { return languageManager; } public LanguageManager getLanguageManager() { return languageManager; }
public DatabaseManager getDatabaseManager() { return databaseManager; } public DatabaseManager getDatabaseManager() { return databaseManager; }

View File

@@ -9,17 +9,22 @@ import de.ticketsystem.model.TicketComment;
import de.ticketsystem.model.TicketPriority; import de.ticketsystem.model.TicketPriority;
import de.ticketsystem.manager.CategoryManager; import de.ticketsystem.manager.CategoryManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID;
public class TicketCommand implements CommandExecutor, TabCompleter { public class TicketCommand implements CommandExecutor, TabCompleter {
@@ -31,15 +36,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
// ── Subkommando-Auflösung ───────────────────────────────────────────── // ── Subkommando-Auflösung ─────────────────────────────────────────────
/**
* Normalisiert ein Subkommando auf den internen englischen Schlüssel.
* Deutsche UND englische Varianten werden immer akzeptiert unabhängig
* von der language-Einstellung. So kann ein Admin auf einem EN-Server
* trotzdem den deutschen Begriff tippen ohne einen Fehler zu bekommen.
*/
private String normalize(String input) { private String normalize(String input) {
return switch (input.toLowerCase()) { return switch (input.toLowerCase()) {
// Deutsch → intern
case "erstellen" -> "create"; case "erstellen" -> "create";
case "liste" -> "list"; case "liste" -> "list";
case "übernehmen", "uebernehmen", case "übernehmen", "uebernehmen",
@@ -59,7 +57,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
case "priorität", "prioritaet" -> "setpriority"; case "priorität", "prioritaet" -> "setpriority";
case "hilfe" -> "help"; case "hilfe" -> "help";
case "kategorie" -> "category"; case "kategorie" -> "category";
// Englisch + alles andere → unverändert
default -> input.toLowerCase(); default -> input.toLowerCase();
}; };
} }
@@ -68,17 +65,36 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(plugin.lang().get("general.console-only"));
return true;
}
if (args.length == 0) { if (args.length == 0) {
if (sender instanceof Player player) {
plugin.getTicketManager().sendHelpMessage(player); plugin.getTicketManager().sendHelpMessage(player);
} else {
sendConsoleHelp(sender);
}
return true; return true;
} }
switch (normalize(args[0])) { String sub = normalize(args[0]);
// ── Konsolen-only: create mit Spielername ────────────────────────
if (sender instanceof ConsoleCommandSender) {
switch (sub) {
case "create" -> { handleCreateConsole(sender, args); return true; }
case "reload" -> { handleReloadConsole(sender); return true; }
case "archive" -> { handleArchiveConsole(sender); return true; }
case "stats" -> { handleStatsConsole(sender); return true; }
default -> {
sender.sendMessage("[TicketSystem] Konsolen-Befehle: create <Spieler> [Kategorie] <Text> | reload | archive | stats");
return true;
}
}
}
// ── Spieler-Befehle ──────────────────────────────────────────────
Player player = (Player) sender;
switch (sub) {
case "create" -> handleCreate(player, args); case "create" -> handleCreate(player, args);
case "list" -> handleList(player); case "list" -> handleList(player);
case "claim" -> handleClaim(player, args); case "claim" -> handleClaim(player, args);
@@ -103,6 +119,179 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
return true; return true;
} }
// ── /ticket create (Konsole) ──────────────────────────────────────────
//
// Syntax: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>
//
// Das Ticket wird im Namen des angegebenen Spielers erstellt.
// Als Erstell-Position wird die aktuelle Spieler-Location verwendet
// (oder eine Default-Location wenn der Spieler offline ist).
private void handleCreateConsole(CommandSender console, String[] args) {
// args[0] = "create", args[1] = Spielername, args[2..] = [Kategorie] [Priorität] Text
if (args.length < 3) {
console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>");
console.sendMessage("[TicketSystem] Beispiel: /ticket create Notch bug high Spieler fällt durch den Boden");
return;
}
String targetName = args[1];
// Spieler suchen (online bevorzugt, sonst OfflinePlayer)
@SuppressWarnings("deprecation")
OfflinePlayer offlineTarget = Bukkit.getOfflinePlayer(targetName);
UUID targetUUID = offlineTarget.getUniqueId();
String resolvedName = offlineTarget.getName() != null ? offlineTarget.getName() : targetName;
CategoryManager cm = plugin.getCategoryManager();
ConfigCategory category = cm.getDefault();
TicketPriority priority = TicketPriority.NORMAL;
int msgStart = 2; // Index ab dem die Beschreibung beginnt (relativ zu args[2..])
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
// args[2] = optional Kategorie, args[3] = optional Priorität, Rest = Text
if (args.length >= 4 && categoriesOn) {
ConfigCategory parsedCat = cm.resolve(args[2]);
if (parsedCat != null) {
category = parsedCat;
msgStart = 3;
if (prioritiesOn && args.length >= 5) {
TicketPriority parsedPrio = parsePriority(args[3]);
if (parsedPrio != null) { priority = parsedPrio; msgStart = 4; }
}
} else if (prioritiesOn) {
TicketPriority parsedPrio = parsePriority(args[2]);
if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; }
}
} else if (args.length >= 4 && prioritiesOn) {
TicketPriority parsedPrio = parsePriority(args[2]);
if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; }
}
// Beschreibung zusammensetzen (args[msgStart+1] weil args[1]=Spielername)
int absoluteStart = msgStart + 1; // +1 für den Spielernamen in args[1]
if (absoluteStart >= args.length) {
console.sendMessage("[TicketSystem] Fehler: Keine Beschreibung angegeben.");
console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>");
return;
}
String message = String.join(" ", Arrays.copyOfRange(args, absoluteStart, args.length));
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
if (message.isEmpty()) {
console.sendMessage("[TicketSystem] Fehler: Beschreibung darf nicht leer sein.");
return;
}
if (message.length() > maxLen) {
console.sendMessage("[TicketSystem] Fehler: Beschreibung zu lang (max. " + maxLen + " Zeichen).");
return;
}
// Location: online-Spieler → aktuelle Pos; offline → Spawn der Default-Welt
Location location;
Player onlineTarget = Bukkit.getPlayer(targetUUID);
if (onlineTarget != null) {
location = onlineTarget.getLocation();
} else {
World defaultWorld = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
location = defaultWorld != null
? defaultWorld.getSpawnLocation()
: new Location(null, 0, 64, 0);
}
final ConfigCategory fCat = category;
final TicketPriority fPrio = priority;
final UUID fUUID = targetUUID;
final String fName = resolvedName;
final Location fLocation = location;
final String fMessage = message;
Ticket ticket = new Ticket(fUUID, fName, fMessage, fLocation);
ticket.setCategoryKey(fCat.getKey());
ticket.setPriority(fPrio);
ticket.setServerName(plugin.getServerName());
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int id = plugin.getDatabaseManager().createTicket(ticket);
if (id == -1) {
console.sendMessage("[TicketSystem] Fehler: Ticket konnte nicht in der Datenbank gespeichert werden.");
return;
}
ticket.setId(id);
plugin.getTicketCache().put(ticket);
Bukkit.getScheduler().runTask(plugin, () -> {
boolean catOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioOn = plugin.getConfig().getBoolean("priorities-enabled", true);
String catInfo = catOn ? " [" + fCat.getName() + "]" : "";
String prioInfo = prioOn ? " [" + fPrio.getDisplayName() + "]" : "";
console.sendMessage("[TicketSystem] Ticket #" + id + " für Spieler '" + fName
+ "' erstellt." + catInfo + prioInfo);
console.sendMessage("[TicketSystem] Nachricht: " + fMessage);
// Team benachrichtigen (gleich wie bei normalem /ticket create)
plugin.getTicketManager().notifyTeam(ticket);
});
});
}
// ── /ticket reload (Konsole) ──────────────────────────────────────────
private void handleReloadConsole(CommandSender console) {
plugin.reloadConfig();
plugin.lang().reload();
plugin.reloadSettings();
plugin.getTicketGUI().reloadConfig();
plugin.getTicketGUI().reloadTitles();
plugin.getFaqGUI().reloadConfig();
plugin.getCategoryManager().reload();
plugin.getFaqManager().reload();
plugin.getTicketCache().clear();
console.sendMessage("[TicketSystem] Konfiguration erfolgreich neu geladen.");
}
// ── /ticket archive (Konsole) ─────────────────────────────────────────
private void handleArchiveConsole(CommandSender console) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int count = plugin.getDatabaseManager().archiveClosedTickets();
Bukkit.getScheduler().runTask(plugin, () ->
console.sendMessage("[TicketSystem] " + count + " Ticket(s) archiviert."));
});
}
// ── /ticket stats (Konsole) ───────────────────────────────────────────
private void handleStatsConsole(CommandSender console) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
var stats = plugin.getDatabaseManager().getTicketStats();
Bukkit.getScheduler().runTask(plugin, () -> {
console.sendMessage("[TicketSystem] === Statistiken ===");
console.sendMessage("[TicketSystem] Gesamt: " + stats.total);
console.sendMessage("[TicketSystem] Offen: " + stats.open);
console.sendMessage("[TicketSystem] Geschlossen: " + stats.closed);
console.sendMessage("[TicketSystem] Weitergeleitet: " + stats.forwarded);
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
console.sendMessage("[TicketSystem] Bewertungen: 👍 " + stats.thumbsUp + " 👎 " + stats.thumbsDown);
}
});
});
}
// ── Konsolen-Hilfe ────────────────────────────────────────────────────
private void sendConsoleHelp(CommandSender console) {
console.sendMessage("[TicketSystem] Verfügbare Konsolen-Befehle:");
console.sendMessage("[TicketSystem] /ticket create <Spieler> [Kategorie] [Priorität] <Text>");
console.sendMessage("[TicketSystem] /ticket reload");
console.sendMessage("[TicketSystem] /ticket archive");
console.sendMessage("[TicketSystem] /ticket stats");
}
// ── /ticket create ──────────────────────────────────────────────────── // ── /ticket create ────────────────────────────────────────────────────
private void handleCreate(Player player, String[] args) { private void handleCreate(Player player, String[] args) {
@@ -535,17 +724,12 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (!player.hasPermission("ticket.admin")) { if (!player.hasPermission("ticket.admin")) {
plugin.lang().send(player, "general.no-permission"); return; plugin.lang().send(player, "general.no-permission"); return;
} }
// Reihenfolge zwingend:
// 1. Config neu laden (liest language neu ein)
// 2. lang().reload() (liest language aus der frischen Config, baut cmdNames neu)
// 3. GUI reloadConfig() (liest Rows/Slots aus der frischen Config)
// 4. weitere Manager (nutzen ggf. frische lang-Texte)
plugin.reloadConfig(); plugin.reloadConfig();
plugin.lang().reload(); plugin.lang().reload();
plugin.reloadSettings(); // serverName & debug-Flag aktualisieren plugin.reloadSettings();
plugin.getTicketGUI().reloadConfig(); // Slots, Rows & Materialien neu laden plugin.getTicketGUI().reloadConfig();
plugin.getTicketGUI().reloadTitles(); // Inventar-Titel aktualisieren plugin.getTicketGUI().reloadTitles();
plugin.getFaqGUI().reloadConfig(); // FAQ-Slots, Rows & Materialien neu laden plugin.getFaqGUI().reloadConfig();
plugin.getCategoryManager().reload(); plugin.getCategoryManager().reload();
plugin.getFaqManager().reload(); plugin.getFaqManager().reload();
plugin.getTicketCache().clear(); plugin.getTicketCache().clear();
@@ -565,8 +749,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
java.io.File backup = plugin.getDatabaseManager().createBackup(); java.io.File backup = plugin.getDatabaseManager().createBackup();
Bukkit.getScheduler().runTask(plugin, () -> { Bukkit.getScheduler().runTask(plugin, () -> {
if (backup != null) { if (backup != null) {
player.sendMessage(plugin.lang().format("backup.success", player.sendMessage(plugin.lang().format("backup.success", "{file}", backup.getName()));
"{file}", backup.getName()));
} else { } else {
player.sendMessage(plugin.lang().get("backup.fail")); player.sendMessage(plugin.lang().get("backup.fail"));
} }
@@ -761,6 +944,12 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
// ── /ticket faq ─────────────────────────────────────────────────────── // ── /ticket faq ───────────────────────────────────────────────────────
private void handleFaq(Player player, String[] args) { private void handleFaq(Player player, String[] args) {
// FAQ-System global deaktiviert?
if (!plugin.getConfig().getBoolean("faq-enabled", true)) {
plugin.lang().send(player, "faq.disabled");
return;
}
if (args.length == 1) { if (args.length == 1) {
plugin.getFaqGUI().openFaqGUI(player); plugin.getFaqGUI().openFaqGUI(player);
return; return;
@@ -777,7 +966,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
player.sendMessage(plugin.lang().get("faq.usage-add-example")); return; player.sendMessage(plugin.lang().get("faq.usage-add-example")); return;
} }
// Prüfen ob args[2] ein bekannter FAQ-Kategorie-Schlüssel ist
String faqCatKey = null; String faqCatKey = null;
int textStart = 2; int textStart = 2;
if (plugin.getFaqManager().hasCategoriesEnabled()) { if (plugin.getFaqManager().hasCategoriesEnabled()) {
@@ -861,11 +1049,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (!player.hasPermission("ticket.admin")) { if (!player.hasPermission("ticket.admin")) {
plugin.lang().send(player, "general.no-permission"); return; plugin.lang().send(player, "general.no-permission"); return;
} }
// Richtung bestimmen: tofile oder tomysql (Standard)
String direction = args.length >= 3 ? args[2].toLowerCase() : "tomysql"; String direction = args.length >= 3 ? args[2].toLowerCase() : "tomysql";
if (direction.equals("tofile") || direction.equals("zudatei")) { if (direction.equals("tofile") || direction.equals("zudatei")) {
// MySQL → faqs.yml
if (!plugin.getFaqManager().isUsingMySQL()) { if (!plugin.getFaqManager().isUsingMySQL()) {
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return; player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
} }
@@ -883,7 +1069,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
}); });
}); });
} else { } else {
// faqs.yml → MySQL
if (!plugin.getFaqManager().isUsingMySQL()) { if (!plugin.getFaqManager().isUsingMySQL()) {
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return; player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
} }
@@ -931,19 +1116,16 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
} }
} }
// ── /ticket kategorie (category) ───────────────────────────────────── // ── /ticket kategorie ─────────────────────────────────────────────────
//
// /ticket kategorie add <Name> [Farbe] [Beschreibung]
// Farbe optional, z.B. &b (Standard: &7)
// Beschreibung optional, alles nach der Farbe
//
// /ticket kategorie delete <Schlüssel>
// /ticket kategorie list
private void handleFaqCategory(Player player, String[] args) { private void handleFaqCategory(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) { if (!player.hasPermission("ticket.admin")) {
plugin.lang().send(player, "general.no-permission"); return; plugin.lang().send(player, "general.no-permission"); return;
} }
// FAQ-System deaktiviert?
if (!plugin.getConfig().getBoolean("faq-enabled", true)) {
plugin.lang().send(player, "faq.disabled"); return;
}
if (args.length < 2) { if (args.length < 2) {
player.sendMessage(plugin.lang().get("faqcat.usage")); return; player.sendMessage(plugin.lang().get("faqcat.usage")); return;
} }
@@ -951,7 +1133,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
switch (args[1].toLowerCase()) { switch (args[1].toLowerCase()) {
case "add", "hinzufügen", "hinzufuegen" -> { case "add", "hinzufügen", "hinzufuegen" -> {
// /ticket kategorie add <Name> [&Farbe] [Beschreibung...]
if (args.length < 3) { if (args.length < 3) {
player.sendMessage(plugin.lang().get("faqcat.usage-add")); return; player.sendMessage(plugin.lang().get("faqcat.usage-add")); return;
} }
@@ -1069,9 +1250,25 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
@Override @Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
List<String> completions = new ArrayList<>(); List<String> completions = new ArrayList<>();
// Konsolen-Tab-Completion
if (sender instanceof ConsoleCommandSender) {
if (args.length == 1) {
for (String s : List.of("create", "reload", "archive", "stats"))
if (s.startsWith(args[0].toLowerCase())) completions.add(s);
} else if (args.length == 2 && normalize(args[0]).equals("create")) {
// Spielernamen vorschlagen
for (Player p : Bukkit.getOnlinePlayers())
if (p.getName().toLowerCase().startsWith(args[1].toLowerCase())) completions.add(p.getName());
} else if (args.length == 3 && normalize(args[0]).equals("create")) {
// Kategorien vorschlagen
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[2]));
}
return completions;
}
if (!(sender instanceof Player player)) return completions; if (!(sender instanceof Player player)) return completions;
// Nur die in der Config eingestellte Sprache verwenden
final boolean useDe = plugin.lang().acceptsGerman(); final boolean useDe = plugin.lang().acceptsGerman();
final boolean useEn = plugin.lang().acceptsEnglish(); final boolean useEn = plugin.lang().acceptsEnglish();
@@ -1091,11 +1288,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (useDe) subs.add("priorität"); if (useDe) subs.add("priorität");
} }
if (player.hasPermission("ticket.admin")) {
if (useEn) subs.addAll(List.of("faq"));
if (useDe) subs.addAll(List.of("faq"));
}
if (player.hasPermission("ticket.admin")) { if (player.hasPermission("ticket.admin")) {
subs.add("kategorie"); subs.add("kategorie");
if (useEn) subs.add("category"); if (useEn) subs.add("category");
@@ -1123,7 +1315,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
&& (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("hinzufügen") && (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("hinzufügen")
|| args[1].equalsIgnoreCase("hinzufuegen")) || args[1].equalsIgnoreCase("hinzufuegen"))
&& player.hasPermission("ticket.admin")) { && player.hasPermission("ticket.admin")) {
// /ticket faq add <TAB> → FAQ-Kategorie-Schlüssel vorschlagen
completions.addAll(getFaqCategoryKeysForTab(args[2])); completions.addAll(getFaqCategoryKeysForTab(args[2]));
} else if (args.length == 3 && normalize(args[0]).equals("faq") } else if (args.length == 3 && normalize(args[0]).equals("faq")
@@ -1136,7 +1327,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
} else if (args.length == 2 && (normalize(args[0]).equals("category") } else if (args.length == 2 && (normalize(args[0]).equals("category")
|| args[0].equalsIgnoreCase("kategorie")) || args[0].equalsIgnoreCase("kategorie"))
&& player.hasPermission("ticket.admin")) { && player.hasPermission("ticket.admin")) {
// /ticket kategorie <TAB>
if (useEn) completions.addAll(List.of("add", "delete", "list")); if (useEn) completions.addAll(List.of("add", "delete", "list"));
if (useDe) completions.addAll(List.of("hinzufügen", "löschen", "liste")); if (useDe) completions.addAll(List.of("hinzufügen", "löschen", "liste"));
completions.removeIf(s -> !s.startsWith(args[1].toLowerCase())); completions.removeIf(s -> !s.startsWith(args[1].toLowerCase()));
@@ -1146,21 +1336,17 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
&& (args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("remove") && (args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("remove")
|| args[1].equalsIgnoreCase("löschen") || args[1].equalsIgnoreCase("loeschen")) || args[1].equalsIgnoreCase("löschen") || args[1].equalsIgnoreCase("loeschen"))
&& player.hasPermission("ticket.admin")) { && player.hasPermission("ticket.admin")) {
// /ticket kategorie delete <TAB> → vorhandene Schlüssel
completions.addAll(getFaqCategoryKeysForTab(args[2])); completions.addAll(getFaqCategoryKeysForTab(args[2]));
} else if (args.length == 2 && normalize(args[0]).equals("create")) { } else if (args.length == 2 && normalize(args[0]).equals("create")) {
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true); boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true); boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
// Wenn Kategorien aktiviert: zeige nur Kategorien-Anzeigenamen bei args[1]
if (categoriesOn) { if (categoriesOn) {
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[1])); completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[1]));
} else if (prioritiesOn) { } else if (prioritiesOn) {
// Wenn nur Prioritäten aktiviert (keine Kategorien): zeige Prioritäten bei args[1]
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false); boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
boolean isTeam = sender instanceof Player p && boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
(p.hasPermission("ticket.support") || p.hasPermission("ticket.admin"));
if (allowPlayersPrio || isTeam) { if (allowPlayersPrio || isTeam) {
completions.addAll(getPriorityInputsForTab(args[1])); completions.addAll(getPriorityInputsForTab(args[1]));
} }
@@ -1170,11 +1356,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true); boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true); boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
// Wenn beide aktiviert: args[2] = Priorität
if (categoriesOn && prioritiesOn) { if (categoriesOn && prioritiesOn) {
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false); boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
boolean isTeam = sender instanceof Player p && boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
(p.hasPermission("ticket.support") || p.hasPermission("ticket.admin"));
if (allowPlayersPrio || isTeam) { if (allowPlayersPrio || isTeam) {
completions.addAll(getPriorityInputsForTab(args[2])); completions.addAll(getPriorityInputsForTab(args[2]));
} }

View File

@@ -1,8 +1,14 @@
package de.ticketsystem.manager; package de.ticketsystem.manager;
import de.ticketsystem.TicketPlugin; import de.ticketsystem.TicketPlugin;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -20,8 +26,13 @@ import java.util.regex.Pattern;
* Lädt alle Plugin-Texte aus der aktiven Sprachdatei (lang_de.yml / lang_en.yml) * Lädt alle Plugin-Texte aus der aktiven Sprachdatei (lang_de.yml / lang_en.yml)
* und ersetzt {cmd_X}-Platzhalter durch die passenden Befehlsnamen. * und ersetzt {cmd_X}-Platzhalter durch die passenden Befehlsnamen.
* *
* Unterstützt Hex-Farbcodes (z.B. &#FF0055 oder <#FF0055>). * Unterstützt:
* Funktioniert ab Spigot 1.16+. * - &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>)
* - MiniMessage-Tags wie <click:run_command:...>, <hover:show_text:...>, <bold> etc.
* → Texte mit MiniMessage-Tags werden als Adventure-Component gesendet,
* damit click/hover-Events tatsächlich funktionieren.
*
* Funktioniert ab Paper/Spigot 1.16+ (Adventure-API vorausgesetzt, ab 1.18 eingebaut).
* *
* ┌─────────────────────────────────────────────────────────┐ * ┌─────────────────────────────────────────────────────────┐
* │ Einziger Konfigurations-Schlüssel: language │ * │ Einziger Konfigurations-Schlüssel: language │
@@ -29,9 +40,6 @@ import java.util.regex.Pattern;
* │ language: de → deutsche Texte + deutsche Befehle │ * │ language: de → deutsche Texte + deutsche Befehle │
* │ language: en → englische Texte + englische Befehle │ * │ language: en → englische Texte + englische Befehle │
* │ language: both → deutsche Texte + beide Befehlsnamen │ * │ language: both → deutsche Texte + beide Befehlsnamen │
* │ │
* │ "command-language" existiert nicht mehr und wird │
* │ vollständig ignoriert. │
* └─────────────────────────────────────────────────────────┘ * └─────────────────────────────────────────────────────────┘
* *
* Verfügbare {cmd_X}-Platzhalter in lang.yml: * Verfügbare {cmd_X}-Platzhalter in lang.yml:
@@ -53,6 +61,17 @@ public class LanguageManager {
"blacklist.list-", "gui.", "join.pending-header", "update." "blacklist.list-", "gui.", "join.pending-header", "update."
}; };
/**
* Erkennt MiniMessage-Tags, die interaktive Inhalte enthalten:
* click, hover, insertion. Nur wenn solche Tags vorhanden sind,
* wird die Nachricht als Component (statt als Legacy-String) gesendet.
*/
private static final Pattern MINIMESSAGE_INTERACTIVE =
Pattern.compile("<(?:click|hover|insertion)[^>]*>", Pattern.CASE_INSENSITIVE);
/** MiniMessage-Instanz (singleton, thread-safe). */
private static final MiniMessage MINI = MiniMessage.miniMessage();
// ── Befehlsnamen-Tabellen (statisch, ändern sich nie) ─────────────────── // ── Befehlsnamen-Tabellen (statisch, ändern sich nie) ───────────────────
private static final LinkedHashMap<String, String> DE = new LinkedHashMap<>(); private static final LinkedHashMap<String, String> DE = new LinkedHashMap<>();
@@ -104,7 +123,6 @@ public class LanguageManager {
/** /**
* Aktiver Sprachmodus wird bei jedem load() DIREKT aus der Config gelesen. * Aktiver Sprachmodus wird bei jedem load() DIREKT aus der Config gelesen.
* Kein Cache, kein Zwischenwert. Immer frisch nach reloadConfig().
*/ */
private String activeLang; private String activeLang;
@@ -120,23 +138,26 @@ public class LanguageManager {
*/ */
private Map<String, String> cmdNames = new LinkedHashMap<>(); private Map<String, String> cmdNames = new LinkedHashMap<>();
/**
* BukkitAudiences Brücke zwischen Adventure-Components und Spigot.
* Muss beim Plugin-onDisable via audiences.close() geschlossen werden.
* Siehe TicketPlugin.onDisable().
*/
private final BukkitAudiences audiences;
// ── Konstruktor ────────────────────────────────────────────────────────── // ── Konstruktor ──────────────────────────────────────────────────────────
public LanguageManager(TicketPlugin plugin) { public LanguageManager(TicketPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.audiences = BukkitAudiences.create(plugin);
load(); load();
} }
// ── Laden ──────────────────────────────────────────────────────────────── // ── Laden ────────────────────────────────────────────────────────────────
/**
* Lädt (oder relädt) die Sprachdatei und baut alle Befehlsnamen neu.
* Muss nach plugin.reloadConfig() aufgerufen werden, damit die frische
* language-Einstellung übernommen wird.
*/
public void load() { public void load() {
// 1. language aus der (bereits neu geladenen) Config lesen // 1. language aus der Config lesen
String raw = plugin.getConfig().getString("language", FALLBACK) String raw = plugin.getConfig().getString("language", FALLBACK)
.toLowerCase().trim(); .toLowerCase().trim();
@@ -204,20 +225,13 @@ public class LanguageManager {
// ── Befehlsnamen ───────────────────────────────────────────────────────── // ── Befehlsnamen ─────────────────────────────────────────────────────────
/**
* Baut {cmd_X} → Anzeigename anhand von activeLang.
*
* de → /ticket erstellen
* en → /ticket create
* both → /ticket create §8(§7erstellen§8)
*/
private Map<String, String> buildCmdNames() { private Map<String, String> buildCmdNames() {
Map<String, String> map = new LinkedHashMap<>(); Map<String, String> map = new LinkedHashMap<>();
for (String key : EN.keySet()) { for (String key : EN.keySet()) {
String display = switch (activeLang) { String display = switch (activeLang) {
case "en" -> "/ticket " + EN.get(key); case "en" -> "/ticket " + EN.get(key);
case "both" -> "/ticket " + EN.get(key) + " §8(§7" + DE.get(key) + "§8)"; case "both" -> "/ticket " + EN.get(key) + " §8(§7" + DE.get(key) + "§8)";
default -> "/ticket " + DE.get(key); // "de" + alle unbekannten default -> "/ticket " + DE.get(key);
}; };
map.put("{cmd_" + key + "}", display); map.put("{cmd_" + key + "}", display);
} }
@@ -232,12 +246,9 @@ public class LanguageManager {
}; };
} }
// ── Befehlssprache-Abfragen (für TicketCommand) ────────────────────────── // ── Befehlssprache-Abfragen ──────────────────────────────────────────────
/** true wenn deutsche Subkommandos akzeptiert werden sollen (Tab-Complete & Eingabe). */
public boolean acceptsGerman() { return "de".equals(activeLang) || "both".equals(activeLang); } public boolean acceptsGerman() { return "de".equals(activeLang) || "both".equals(activeLang); }
/** true wenn englische Subkommandos akzeptiert werden sollen (Tab-Complete & Eingabe). */
public boolean acceptsEnglish() { return "en".equals(activeLang) || "both".equals(activeLang); } public boolean acceptsEnglish() { return "en".equals(activeLang) || "both".equals(activeLang); }
// ── Interne Platzhalter-Ersetzung ─────────────────────────────────────── // ── Interne Platzhalter-Ersetzung ───────────────────────────────────────
@@ -281,11 +292,48 @@ public class LanguageManager {
return prefix + format(key, replacements); return prefix + format(key, replacements);
} }
/** Sendet eine Nachricht (mit Prefix wenn nötig) an einen CommandSender. */ /**
* Sendet eine Nachricht an einen CommandSender.
*
* Enthält der Rohtext MiniMessage-Tags mit interaktiven Elementen
* (click, hover, insertion), wird die Nachricht als Adventure-Component
* gesendet damit run_command, suggest_command und hover-Texte
* tatsächlich funktionieren.
*
* Enthält er keine solchen Tags, wird der klassische Legacy-Pfad
* (§-Codes) verwendet volle Abwärtskompatibilität.
*/
public void send(CommandSender sender, String key, String... replacements) { public void send(CommandSender sender, String key, String... replacements) {
sender.sendMessage(needsPrefix(key) String raw = applyCmdNames(getRaw(key));
? prefix + format(key, replacements)
: format(key, replacements)); // {placeholder}-Ersetzungen anwenden (vor MiniMessage-Parsing!)
if (replacements.length % 2 != 0)
plugin.getLogger().warning("[LanguageManager] send() benötigt eine gerade Anzahl an Argumenten für: " + key);
for (int i = 0; i + 1 < replacements.length; i += 2)
raw = raw.replace(replacements[i], replacements[i + 1]);
if (hasMiniMessageInteractiveTags(raw)) {
// MiniMessage-Pfad: interaktive Komponenten (click/hover)
// Legacy-&-Farbcodes in MiniMessage-Äquivalente umwandeln damit
// beide Syntax-Varianten in derselben Nachricht funktionieren.
String mm = legacyToMiniMessage(raw);
Component component = MINI.deserialize(mm);
// Prefix als Component voranstellen wenn nötig
if (needsPrefix(key)) {
Component prefixComp = MINI.deserialize(legacyToMiniMessage(
color("&8[&6Ticket&8] &r")));
component = prefixComp.append(component);
}
// BukkitAudiences sendet korrekt auf Spigot UND Paper
Audience audience = audiences.sender(sender);
audience.sendMessage(component);
} else {
// Legacy-Pfad: §-Codes, kein Adventure nötig
String text = color(raw);
sender.sendMessage(needsPrefix(key) ? prefix + text : text);
}
} }
/** Sendet die Trennlinie. */ /** Sendet die Trennlinie. */
@@ -304,11 +352,12 @@ public class LanguageManager {
/** /**
* Übersetzt &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>) in §-Codes. * Übersetzt &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>) in §-Codes.
* Wird für alle Texte verwendet die NICHT als Component gesendet werden.
*/ */
public String color(String text) { public String color(String text) {
if (text == null || text.isEmpty()) return ""; if (text == null || text.isEmpty()) return "";
// Regex für Hex Codes: &#RRGGBB oder <#RRGGBB> // Hex-Codes: &#RRGGBB oder <#RRGGBB>
Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})|<#([A-Fa-f0-9]{6})>"); Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})|<#([A-Fa-f0-9]{6})>");
Matcher matcher = hexPattern.matcher(text); Matcher matcher = hexPattern.matcher(text);
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
@@ -316,29 +365,126 @@ public class LanguageManager {
while (matcher.find()) { while (matcher.find()) {
String group = matcher.group(1) != null ? matcher.group(1) : matcher.group(2); String group = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
try { try {
// Fix: Explizite Nutzung von net.md_5.bungee.api.ChatColor für Hex-Support (Spigot 1.16+)
// Dies verhindert den "Symbol nicht gefunden"-Fehler beim Kompilieren mit der reinen Bukkit-API.
net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of("#" + group); net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of("#" + group);
matcher.appendReplacement(buffer, hexColor.toString()); matcher.appendReplacement(buffer, hexColor.toString());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Falls der Farbcode ungültig ist, Tag entfernen
matcher.appendReplacement(buffer, ""); matcher.appendReplacement(buffer, "");
} }
} }
String parsed = matcher.appendTail(buffer).toString(); String parsed = matcher.appendTail(buffer).toString();
// Übersetzung der klassischen &-Farbcodes (Bukkit Standard)
return org.bukkit.ChatColor.translateAlternateColorCodes('&', parsed); return org.bukkit.ChatColor.translateAlternateColorCodes('&', parsed);
} }
public String getPrefix() { return prefix; } public String getPrefix() { return prefix; }
public String getActiveLang() { return activeLang; } public String getActiveLang() { return activeLang; }
public String getFileLang() { return fileLang; } public String getFileLang() { return fileLang; }
public BukkitAudiences getAudiences() { return audiences; }
/** Relädt die Sprachdatei. Muss NACH plugin.reloadConfig() aufgerufen werden. */ /** Relädt die Sprachdatei. Muss NACH plugin.reloadConfig() aufgerufen werden. */
public void reload() { load(); } public void reload() { load(); }
// ── MiniMessage-Hilfsmethoden ────────────────────────────────────────────
/**
* Prüft ob ein Text MiniMessage-Tags mit interaktiven Inhalten enthält
* (click, hover, insertion). Nur dann lohnt sich der Component-Pfad.
*/
private boolean hasMiniMessageInteractiveTags(String text) {
if (text == null) return false;
return MINIMESSAGE_INTERACTIVE.matcher(text).find();
}
/**
* Konvertiert &-Farbcodes und &#RRGGBB/<#RRGGBB>-Hex-Codes in MiniMessage-Syntax,
* damit ein Text der beide Formate mischt korrekt von MiniMessage geparst werden kann.
*
* Beispiele:
* &6Hallo → <gold>Hallo
* &#FF5500Text → <#FF5500>Text
* &lFett → <bold>Fett
* &rReset → <reset>Reset
*/
private String legacyToMiniMessage(String text) {
if (text == null) return "";
// 1. &#RRGGBB → <#RRGGBB>
text = text.replaceAll("&#([A-Fa-f0-9]{6})", "<#$1>");
// 2. §-Codes (bereits konvertierte Hex-Codes §x§R§G§B... überspringen wir hier,
// da sie nicht in MiniMessage passen nur &-Shorthand-Codes mappen)
// Wir ersetzen &X-Codes durch ihre MiniMessage-Äquivalente.
StringBuilder sb = new StringBuilder();
char[] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++) {
if ((chars[i] == '&' || chars[i] == '§') && i + 1 < chars.length) {
char code = Character.toLowerCase(chars[i + 1]);
String mm = switch (code) {
case '0' -> "<black>";
case '1' -> "<dark_blue>";
case '2' -> "<dark_green>";
case '3' -> "<dark_aqua>";
case '4' -> "<dark_red>";
case '5' -> "<dark_purple>";
case '6' -> "<gold>";
case '7' -> "<gray>";
case '8' -> "<dark_gray>";
case '9' -> "<blue>";
case 'a' -> "<green>";
case 'b' -> "<aqua>";
case 'c' -> "<red>";
case 'd' -> "<light_purple>";
case 'e' -> "<yellow>";
case 'f' -> "<white>";
case 'l' -> "<bold>";
case 'm' -> "<strikethrough>";
case 'n' -> "<underlined>";
case 'o' -> "<italic>";
case 'r' -> "<reset>";
default -> null;
};
if (mm != null) {
sb.append(mm);
i++; // Code-Zeichen überspringen
continue;
}
}
sb.append(chars[i]);
}
return sb.toString();
}
/**
* Sendet eine Adventure-Component an einen CommandSender.
*
* Der Cast auf net.kyori.adventure.audience.Audience umgeht das compile-time
* Problem, dass Spigots CommandSender-Interface sendMessage(Component) nicht
* direkt exponiert zur Laufzeit implementiert jeder CommandSender auf
* Paper/Spigot 1.18+ das Audience-Interface.
*/
private void sendComponent(CommandSender sender, Component component) {
if (sender instanceof net.kyori.adventure.audience.Audience audience) {
try {
audience.sendMessage(component);
} catch (NoSuchMethodError | AbstractMethodError e) {
// Fallback für reines Spigot ohne Adventure-Patch:
// Component in Legacy-String serialisieren (click/hover gehen verloren,
// aber der Text bleibt lesbar)
String legacy = LegacyComponentSerializer.legacySection().serialize(component);
sender.sendMessage(legacy);
if (plugin.isDebug()) {
plugin.getLogger().info("[LanguageManager] Adventure-API nicht verfügbar "
+ "interaktive Tags werden als Plain-Text gesendet. "
+ "Wechsel auf Paper für volle MiniMessage-Unterstützung.");
}
}
} else {
// Kein Audience (z. B. alte Konsole) → Legacy-Fallback
String legacy = LegacyComponentSerializer.legacySection().serialize(component);
sender.sendMessage(legacy);
}
}
// ── Intern ─────────────────────────────────────────────────────────────── // ── Intern ───────────────────────────────────────────────────────────────
private boolean needsPrefix(String key) { private boolean needsPrefix(String key) {

View File

@@ -209,6 +209,9 @@ discord:
# ============================================================ # ============================================================
# GUI KONFIGURATION (Layouts, Slots, Items) # GUI KONFIGURATION (Layouts, Slots, Items)
# ============================================================ # ============================================================
# FAQ-System global deaktivieren (Standard: true)
faq-enabled: true
# Hier kannst du das Aussehen und die Anordnung der GUIs anpassen. # Hier kannst du das Aussehen und die Anordnung der GUIs anpassen.
# WICHTIG: gui-settings muss ganz links stehen (keine Raute davor!). # WICHTIG: gui-settings muss ganz links stehen (keine Raute davor!).
gui-settings: gui-settings:

View File

@@ -576,7 +576,6 @@ web:
login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden." login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden."
archive-btn-restore: "Wiederherstellen" archive-btn-restore: "Wiederherstellen"
archive-btn-delete: "Permanent löschen" archive-btn-delete: "Permanent löschen"
detail-btn-archive: "Ins Archiv verschieben"
login-label-user: "Benutzername" login-label-user: "Benutzername"
login-label-pass: "Passwort" login-label-pass: "Passwort"
login-btn: "Anmelden" login-btn: "Anmelden"
@@ -615,13 +614,11 @@ web:
tickets-col-prio: "Priorität" tickets-col-prio: "Priorität"
tickets-col-status: "Status" tickets-col-status: "Status"
tickets-col-created: "Erstellt" tickets-col-created: "Erstellt"
nav-archive: "Archiv"
filter-all-status: "Alle Status" filter-all-status: "Alle Status"
filter-open: "Offen" filter-open: "Offen"
filter-claimed: "Angenommen" filter-claimed: "Angenommen"
filter-forwarded: "Weitergeleitet" filter-forwarded: "Weitergeleitet"
filter-closed: "Geschlossen" filter-closed: "Geschlossen"
filter-archived: "Archiviert"
filter-all-cat: "Alle Kategorien" filter-all-cat: "Alle Kategorien"
filter-all-prio: "Alle Prioritäten" filter-all-prio: "Alle Prioritäten"
filter-low: "Niedrig" filter-low: "Niedrig"

View File

@@ -576,7 +576,6 @@ web:
login-error: "Username or password incorrect." login-error: "Username or password incorrect."
archive-btn-restore: "Restore" archive-btn-restore: "Restore"
archive-btn-delete: "Delete permanently" archive-btn-delete: "Delete permanently"
detail-btn-archive: "Move to archive"
login-label-user: "Username" login-label-user: "Username"
login-label-pass: "Password" login-label-pass: "Password"
login-btn: "Sign In" login-btn: "Sign In"
@@ -615,13 +614,11 @@ web:
tickets-col-prio: "Priority" tickets-col-prio: "Priority"
tickets-col-status: "Status" tickets-col-status: "Status"
tickets-col-created: "Created" tickets-col-created: "Created"
nav-archive: "Archive"
filter-all-status: "All Statuses" filter-all-status: "All Statuses"
filter-open: "Open" filter-open: "Open"
filter-claimed: "Claimed" filter-claimed: "Claimed"
filter-forwarded: "Forwarded" filter-forwarded: "Forwarded"
filter-closed: "Closed" filter-closed: "Closed"
filter-archived: "Archived"
filter-all-cat: "All Categories" filter-all-cat: "All Categories"
filter-all-prio: "All Priorities" filter-all-prio: "All Priorities"
filter-low: "Low" filter-low: "Low"

View File

@@ -1,5 +1,5 @@
name: TicketSystem name: TicketSystem
version: 1.1.4 version: 1.1.5
main: de.ticketsystem.TicketPlugin main: de.ticketsystem.TicketPlugin
api-version: 1.20 api-version: 1.20
author: M_Viper author: M_Viper