This commit is contained in:
Nordi98 2025-08-04 09:48:26 +02:00
parent 4d24104e50
commit 05be118a84
2 changed files with 477 additions and 691 deletions

View file

@ -6,6 +6,8 @@ local currentTarget = nil
local nearbyPlayers = {}
local isLicenseShowing = false
local pendingRequests = {}
local currentLicenseData = nil
local currentTargetId = nil
-- Hilfsfunktionen
local function debugPrint(message)
@ -116,6 +118,20 @@ local openDriversLicenseClassMenu
local openRevokeLicenseMenu
local openPlayerLicenseMenu
local openLicenseMenu
local openManualLicenseEntry
local openReactivateLicenseMenu
-- Helper function to get license type options
local function getLicenseTypeOptions()
local options = {}
for licenseType, config in pairs(Config.LicenseTypes) do
table.insert(options, {
value = licenseType,
label = config.label
})
end
return options
end
-- Lizenz-Ausstellung bestätigen (FRÜH DEFINIERT)
confirmIssueLicense = function(targetId, targetName, licenseType, classes)
@ -409,6 +425,51 @@ openRevokeLicenseMenu = function(targetId, targetName, licenses)
lib.showContext('revoke_license')
end
-- Manual License Entry Menu
openManualLicenseEntry = function(targetId, targetName)
debugPrint("Öffne manuelle Lizenz-Eingabe für: " .. targetName)
local input = lib.inputDialog('Lizenz manuell ausstellen', {
{type = 'select', label = 'Lizenztyp', options = getLicenseTypeOptions()},
{type = 'input', label = 'Name', default = targetName},
{type = 'input', label = 'Geburtsdatum', description = 'Format: DD.MM.YYYY'},
{type = 'select', label = 'Geschlecht', options = {
{value = 'male', label = 'Männlich'},
{value = 'female', label = 'Weiblich'},
{value = 'other', label = 'Divers'}
}},
{type = 'date', label = 'Ausstellungsdatum', default = os.date("%Y-%m-%d")},
{type = 'date', label = 'Ablaufdatum', default = os.date("%Y-%m-%d", os.time() + 365*24*60*60)},
{type = 'checkbox', label = 'Foto aufnehmen?'}
})
if not input then return end
local licenseType = input[1]
local licenseData = {
name = input[2],
birthday = input[3],
gender = input[4],
issue_date = os.date("%d.%m.%Y", os.time(os.date("!*t", input[5] / 1000))),
expire_date = os.date("%d.%m.%Y", os.time(os.date("!*t", input[6] / 1000))),
license_type = licenseType
}
if input[7] then -- Take photo
TriggerEvent('license-system:client:openCamera', targetId, licenseData)
else
TriggerServerEvent('license-system:server:issueManualLicense', targetId, licenseData)
end
end
-- License Reactivation Menu
openReactivateLicenseMenu = function(targetId, targetName)
debugPrint("Öffne Lizenz-Reaktivierungs-Menü für: " .. targetName)
-- Request all licenses including inactive ones
TriggerServerEvent('license-system:server:requestAllPlayerLicenses', targetId, true)
end
-- Spieler-Lizenz-Menü
openPlayerLicenseMenu = function(targetId, targetName)
debugPrint("=== openPlayerLicenseMenu START ===")
@ -575,6 +636,15 @@ RegisterNetEvent('license-system:client:receivePlayerLicenses', function(license
end
})
table.insert(menuOptions, {
title = 'Lizenz manuell ausstellen',
description = 'Lizenz mit benutzerdefinierten Daten ausstellen',
icon = 'fas fa-edit',
onSelect = function()
openManualLicenseEntry(targetId, targetName)
end
})
if licenses and #licenses > 0 then
table.insert(menuOptions, {
title = 'Lizenz entziehen',
@ -586,6 +656,15 @@ RegisterNetEvent('license-system:client:receivePlayerLicenses', function(license
})
end
table.insert(menuOptions, {
title = 'Lizenz reaktivieren',
description = 'Eine inaktive Lizenz wieder aktivieren',
icon = 'fas fa-redo',
onSelect = function()
openReactivateLicenseMenu(targetId, targetName)
end
})
table.insert(menuOptions, {
title = '← Zurück',
icon = 'fas fa-arrow-left',
@ -603,6 +682,100 @@ RegisterNetEvent('license-system:client:receivePlayerLicenses', function(license
lib.showContext('player_licenses')
end)
-- EVENT HANDLER: Alle Spieler-Lizenzen (inkl. inaktive) erhalten
RegisterNetEvent('license-system:client:receiveAllPlayerLicenses', function(licenses, targetId, targetName)
debugPrint("=== Event: receiveAllPlayerLicenses ===")
debugPrint("Erhaltene Lizenzen: " .. #licenses)
local menuOptions = {}
if licenses and #licenses > 0 then
for _, license in ipairs(licenses) do
if license.is_active == 0 then -- Only show inactive licenses
local licenseConfig = Config.LicenseTypes[license.license_type] or {
label = license.license_type,
icon = 'fas fa-id-card',
color = '#667eea'
}
table.insert(menuOptions, {
title = licenseConfig.label .. '',
description = 'Status: Ungültig | Ausgestellt: ' .. (license.issue_date or 'Unbekannt'),
icon = licenseConfig.icon,
onSelect = function()
-- Confirmation dialog
lib.registerContext({
id = 'confirm_reactivate_license',
title = 'Lizenz reaktivieren bestätigen',
options = {
{
title = 'Spieler: ' .. targetName,
disabled = true,
icon = 'fas fa-user'
},
{
title = 'Lizenztyp: ' .. licenseConfig.label,
disabled = true,
icon = licenseConfig.icon
},
{
title = '─────────────────',
disabled = true
},
{
title = '✅ Bestätigen',
description = 'Lizenz jetzt reaktivieren',
icon = 'fas fa-check',
onSelect = function()
debugPrint("Sende Lizenz-Reaktivierung an Server...")
TriggerServerEvent('license-system:server:reactivateLicense', targetId, license.license_type)
lib.hideContext()
end
},
{
title = '❌ Abbrechen',
description = 'Vorgang abbrechen',
icon = 'fas fa-times',
onSelect = function()
openReactivateLicenseMenu(targetId, targetName)
end
}
}
})
lib.showContext('confirm_reactivate_license')
end
})
end
end
end
if #menuOptions == 0 then
table.insert(menuOptions, {
title = 'Keine inaktiven Lizenzen',
description = 'Dieser Spieler hat keine inaktiven Lizenzen',
icon = 'fas fa-exclamation-triangle',
disabled = true
})
end
table.insert(menuOptions, {
title = '← Zurück',
icon = 'fas fa-arrow-left',
onSelect = function()
openPlayerLicenseMenu(targetId, targetName)
end
})
lib.registerContext({
id = 'reactivate_license',
title = 'Lizenz reaktivieren: ' .. targetName,
options = menuOptions
})
lib.showContext('reactivate_license')
end)
-- EVENT HANDLER: Berechtigung erhalten
RegisterNetEvent('license-system:client:receivePermission', function(hasAuth, licenseType)
debugPrint("=== Event: receivePermission ===")
@ -639,6 +812,19 @@ RegisterNetEvent('license-system:client:licenseRevoked', function(targetId, lice
end
end)
-- EVENT HANDLER: Lizenz erfolgreich reaktiviert
RegisterNetEvent('license-system:client:licenseReactivated', function(targetId, licenseType)
debugPrint("=== Event: licenseReactivated ===")
debugPrint("Lizenz " .. licenseType .. " für Spieler " .. targetId .. " reaktiviert")
-- Menü aktualisieren
if lib.getOpenContextMenu() then
lib.hideContext()
Wait(100)
openLicenseMenu()
end
end)
-- EVENT HANDLER: Lizenz anzeigen (von anderen Spielern)
RegisterNetEvent('license-system:client:showLicense', function(targetId)
debugPrint("Event erhalten: showLicense für ID " .. tostring(targetId))
@ -652,10 +838,16 @@ RegisterNetEvent('license-system:client:showMyLicense', function(licenseType)
end)
-- EVENT HANDLER: Kamera öffnen
RegisterNetEvent('license-system:client:openCamera', function()
debugPrint("Event erhalten: openCamera")
RegisterNetEvent('license-system:client:openCamera', function(targetId, licenseData)
debugPrint("Event erhalten: openCamera für Spieler " .. tostring(targetId))
-- Store the data temporarily
currentLicenseData = licenseData
currentTargetId = targetId
SendNUIMessage({
action = 'openCamera'
action = 'openCamera',
citizenid = QBCore.Functions.GetPlayerData().citizenid
})
end)
@ -682,14 +874,22 @@ end)
RegisterNUICallback('savePhoto', function(data, cb)
debugPrint("NUI Callback: savePhoto")
if data.photo and data.citizenid then
TriggerServerEvent('license-system:server:savePhoto', data.citizenid, data.photo)
if data.photo and currentLicenseData and currentTargetId then
-- Add photo to license data
currentLicenseData.photo_url = data.photo
-- Issue the license with the photo
TriggerServerEvent('license-system:server:issueManualLicense', currentTargetId, currentLicenseData)
-- Reset temporary data
currentLicenseData = nil
currentTargetId = nil
if cb and type(cb) == "function" then
cb('ok')
end
else
debugPrint("^1Fehler: Foto-Daten unvollständig^7")
debugPrint("^1Fehler: Foto-Daten unvollständig oder keine aktuelle Lizenz-Ausstellung^7")
if cb and type(cb) == "function" then
cb('error')
@ -773,6 +973,10 @@ AddEventHandler('onResourceStop', function(resourceName)
lib.hideContext()
end
-- Reset temporary data
currentLicenseData = nil
currentTargetId = nil
debugPrint("License-System Client gestoppt")
end
end)
@ -1157,6 +1361,10 @@ local function cleanup()
-- Pending Requests leeren
pendingRequests = {}
-- Temporary data leeren
currentLicenseData = nil
currentTargetId = nil
-- Animationen stoppen
local playerPed = PlayerPedId()
if DoesEntityExist(playerPed) then
@ -1237,312 +1445,6 @@ RegisterCommand('licensedebug', function()
showNotification('Debug-Informationen in der Konsole ausgegeben!', 'success')
end, false)
-- Erweiterte Keybind-Behandlung
CreateThread(function()
while true do
Wait(0)
-- ESC-Taste für Lizenz schließen
if isLicenseShowing then
if IsControlJustPressed(0, 322) then -- ESC
closeLicense()
end
end
-- Zusätzliche Hotkeys (falls konfiguriert)
if Config.Keybinds and Config.Keybinds.emergency_close then
if IsControlJustPressed(0, Config.Keybinds.emergency_close.control) then
cleanup()
showNotification('Notfall-Schließung aktiviert!', 'info')
end
end
-- Performance-Optimierung
if not isLicenseShowing and not lib.getOpenContextMenu() then
Wait(500)
end
end
end)
-- Finaler Debug-Output
debugPrint("License-System Client vollständig geladen - Alle Funktionen verfügbar")
-- Erweiterte Netzwerk-Events mit Retry-Mechanismus
local function sendEventWithRetry(eventName, data, maxRetries)
maxRetries = maxRetries or 3
local retries = 0
local function attemptSend()
retries = retries + 1
debugPrint("Sende Event: " .. eventName .. " (Versuch " .. retries .. "/" .. maxRetries .. ")")
TriggerServerEvent(eventName, table.unpack(data or {}))
-- Timeout für Response
CreateThread(function()
Wait(5000) -- 5 Sekunden Timeout
if retries < maxRetries then
debugPrint("^3Timeout für Event " .. eventName .. " - Wiederhole...^7")
attemptSend()
else
debugPrint("^1Maximale Wiederholungen für Event " .. eventName .. " erreicht^7")
showNotification('Netzwerk-Fehler! Bitte versuche es später erneut.', 'error')
end
end)
end
attemptSend()
end
-- Erweiterte Export-Funktionen für andere Resources
exports('hasLicense', function(licenseType)
-- Diese Funktion kann von anderen Resources verwendet werden
local PlayerData = QBCore.Functions.GetPlayerData()
if not PlayerData or not PlayerData.citizenid then return false end
-- Hier würde normalerweise eine Server-Anfrage gemacht werden
-- Für jetzt geben wir false zurück
return false
end)
exports('showPlayerLicense', function(targetId, licenseType)
-- Export für andere Resources um Lizenzen anzuzeigen
if licenseType then
TriggerServerEvent('license-system:server:requestSpecificLicense', targetId, licenseType)
else
showPlayerLicense(targetId)
end
end)
exports('openLicenseMenu', function()
-- Export für andere Resources um das Lizenz-Menü zu öffnen
openLicenseMenu()
end)
-- Neuer Event-Handler für benutzerdefinierte Lizenzen
RegisterNetEvent('license-system:server:issueCustomLicense', function(targetId, licenseType, customData, classes)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
local TargetPlayer = QBCore.Functions.GetPlayer(targetId)
if not Player or not TargetPlayer then
debugPrint("Spieler nicht gefunden: " .. src .. " -> " .. targetId)
return
end
-- Berechtigung prüfen
if not isAuthorized(Player.PlayerData.job.name) then
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type)
return
end
-- Lizenz-Konfiguration prüfen
local config = Config.LicenseTypes[licenseType]
if not config then
debugPrint("Unbekannter Lizenztyp: " .. licenseType)
return
end
debugPrint("Erstelle benutzerdefinierte Lizenz: " .. licenseType .. " für " .. TargetPlayer.PlayerData.citizenid)
-- Benutzerdefinierte Daten validieren und bereinigen
local validatedData = {}
for _, field in ipairs(config.custom_fields or {}) do
local value = customData[field.name]
-- Pflichtfeld-Prüfung
if field.required and (not value or value == "") then
TriggerClientEvent('QBCore:Notify', src, "Feld '" .. field.label .. "' ist erforderlich", "error")
return
end
-- Wert bereinigen und validieren
if value and value ~= "" then
value = string.gsub(value, "'", "''") -- SQL-Injection Schutz
-- Typ-spezifische Validierung
if field.type == "url" and not string.match(value, "^https?://") then
TriggerClientEvent('QBCore:Notify', src, "Ungültige URL in Feld '" .. field.label .. "'", "error")
return
end
if field.type == "date" and not string.match(value, "^%d%d%.%d%d%.%d%d%d%d$") then
TriggerClientEvent('QBCore:Notify', src, "Ungültiges Datum in Feld '" .. field.label .. "'", "error")
return
end
validatedData[field.name] = value
end
end
-- Klassen validieren
local validatedClasses = {}
if config.classes and classes then
for _, class in ipairs(classes) do
if config.classes[class.key] then
table.insert(validatedClasses, class)
end
end
end
-- Lizenz in Datenbank speichern
local success = saveCustomLicenseToDB(
TargetPlayer.PlayerData.citizenid,
licenseType,
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname,
validatedData,
validatedClasses
)
if success then
debugPrint("Benutzerdefinierte Lizenz erfolgreich gespeichert")
-- Cache invalidieren
invalidateCache(TargetPlayer.PlayerData.citizenid, licenseType)
-- Benachrichtigungen
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_issued.message, Config.Notifications.license_issued.type)
TriggerClientEvent('QBCore:Notify', targetId, "Du hast eine " .. config.label .. " erhalten!", "success")
-- Events
TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType)
TriggerClientEvent('license-system:client:refreshMenu', src)
-- Log
debugPrint("Lizenz ausgestellt: " .. licenseType .. " von " .. Player.PlayerData.name .. " an " .. TargetPlayer.PlayerData.name)
else
debugPrint("Fehler beim Speichern der benutzerdefinierten Lizenz")
TriggerClientEvent('QBCore:Notify', src, "Fehler beim Ausstellen der Lizenz", "error")
end
end)
-- Funktion zum Speichern benutzerdefinierter Lizenzen
function saveCustomLicenseToDB(citizenid, licenseType, issuedBy, customData, classes)
local config = Config.LicenseTypes[licenseType]
if not config then return false end
-- Ablaufdatum berechnen
local expireDate = nil
if config.validity_days then
expireDate = os.date('%Y-%m-%d %H:%M:%S', os.time() + (config.validity_days * 24 * 60 * 60))
end
-- Holder-Name aus Custom-Data oder Standard
local holderName = customData.holder_name or "Unbekannt"
return safeDBOperation(function()
-- Alte Lizenzen deaktivieren
local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?"
MySQL.query.await(deactivateQuery, {citizenid, licenseType})
-- Neue Lizenz einfügen
local insertQuery = [[
INSERT INTO player_licenses
(citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, custom_data, holder_name)
VALUES (?, ?, ?, NOW(), ?, ?, 1, ?, ?, ?)
]]
local result = MySQL.insert.await(insertQuery, {
citizenid,
licenseType,
config.label,
expireDate,
issuedBy,
json.encode(classes or {}),
json.encode(customData or {}),
holderName
})
return result and result > 0
end, "Benutzerdefinierte Lizenz speichern")
end
-- Erweiterte Lizenz-Abruf-Funktion
function getLicenseFromDB(citizenid, licenseType, skipCache)
-- Cache prüfen
local cacheKey = citizenid .. "_" .. licenseType
if not skipCache and licenseCache[cacheKey] then
debugPrint("Lizenz aus Cache geladen: " .. licenseType)
return licenseCache[cacheKey]
end
local license = safeDBOperation(function()
local query = [[
SELECT *,
CASE
WHEN expire_date IS NULL THEN 1
WHEN expire_date > NOW() THEN 1
ELSE 0
END as is_valid
FROM player_licenses
WHERE citizenid = ? AND license_type = ? AND is_active = 1
ORDER BY created_at DESC
LIMIT 1
]]
local result = MySQL.query.await(query, {citizenid, licenseType})
return result and result[1] or nil
end, "Lizenz abrufen")
if license then
-- Custom-Data und Classes parsen
if license.custom_data then
license.custom_data_parsed = json.decode(license.custom_data)
end
if license.classes then
license.classes_parsed = json.decode(license.classes)
end
-- Cache speichern
licenseCache[cacheKey] = license
debugPrint("Lizenz in Cache gespeichert: " .. licenseType)
end
return license
end
-- Add to client/main.lua
RegisterNetEvent('license-system:client:openCamera', function(targetId, licenseData)
debugPrint("Event erhalten: openCamera für Spieler " .. tostring(targetId))
-- Store the data temporarily
currentLicenseData = licenseData
currentTargetId = targetId
SendNUIMessage({
action = 'openCamera',
citizenid = QBCore.Functions.GetPlayerData().citizenid
})
end)
-- Enhance the NUI callback for photo saving
RegisterNUICallback('savePhoto', function(data, cb)
debugPrint("NUI Callback: savePhoto")
if data.photo and currentLicenseData and currentTargetId then
-- Add photo to license data
currentLicenseData.photo_url = data.photo
-- Issue the license with the photo
TriggerServerEvent('license-system:server:issueManualLicense', currentTargetId, currentLicenseData)
-- Reset temporary data
currentLicenseData = nil
currentTargetId = nil
if cb and type(cb) == "function" then
cb('ok')
end
else
debugPrint("^1Fehler: Foto-Daten unvollständig oder keine aktuelle Lizenz-Ausstellung^7")
if cb and type(cb) == "function" then
cb('error')
end
end
end)
debugPrint("License-System Server erweitert geladen (Custom License Support)")