From 05be118a84e04390dbcdd58375707bab3ee2c882 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 09:48:26 +0200 Subject: [PATCH] ed --- .../[tools]/nordi_license/client/main.lua | 530 ++++++--------- .../[tools]/nordi_license/server/main.lua | 638 +++++++----------- 2 files changed, 477 insertions(+), 691 deletions(-) diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua index f8b3b634d..1d97b77e5 100644 --- a/resources/[tools]/nordi_license/client/main.lua +++ b/resources/[tools]/nordi_license/client/main.lua @@ -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)") \ No newline at end of file diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index 019636a28..00ee16c85 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -722,11 +722,268 @@ RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licen debugPrint("Lizenz " .. licenseType .. " entzogen von " .. issuerName .. " für " .. targetName) else debugPrint("^1Fehler beim Entziehen der Lizenz^7") - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', - 'error') + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') end end) +-- EVENT HANDLER: Lizenz reaktivieren +RegisterNetEvent('license-system:server:reactivateLicense', function(targetId, licenseType) + local src = source + debugPrint("=== Event: reactivateLicense ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) + + -- Check if player has permission to reactivate this license type + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + + local job = Player.PlayerData.job.name + local canReactivate = false + + -- Check if job can reactivate this license type + if Config.ReactivationPermissions and Config.ReactivationPermissions[job] then + for _, allowedType in ipairs(Config.ReactivationPermissions[job]) do + if allowedType == licenseType then + canReactivate = true + break + end + end + end + + if not canReactivate then + TriggerClientEvent('QBCore:Notify', src, 'Du darfst diesen Lizenztyp nicht reaktivieren!', 'error') + return + end + + -- Get target player + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') + return + end + + local targetCitizenId = targetPlayer.PlayerData.citizenid + + -- Find the most recent license of this type (even if inactive) + local query = [[ + SELECT * FROM player_licenses + WHERE citizenid = ? AND license_type = ? + ORDER BY created_at DESC + LIMIT 1 + ]] + + local result = MySQL.query.await(query, {targetCitizenId, licenseType}) + + if not result or #result == 0 then + TriggerClientEvent('QBCore:Notify', src, 'Keine Lizenz dieses Typs gefunden!', 'error') + return + end + + -- Reactivate the license + local updateQuery = "UPDATE player_licenses SET is_active = 1 WHERE id = ?" + local success = MySQL.update.await(updateQuery, {result[1].id}) + + if success then + -- Invalidate cache + invalidateCache(targetCitizenId, licenseType) + + local targetName = getPlayerName(targetId) + local issuerName = getPlayerName(src) + local config = Config.LicenseTypes[licenseType] + + -- Notifications + TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich reaktiviert für ' .. targetName, 'success') + TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (config.label or licenseType) .. ' wurde reaktiviert!', 'success') + + -- Events + TriggerClientEvent('license-system:client:licenseReactivated', src, targetId, licenseType) + TriggerClientEvent('license-system:client:refreshMenu', src) + + -- Log + debugPrint("Lizenz " .. licenseType .. " reaktiviert von " .. issuerName .. " für " .. targetName) + else + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Reaktivieren der Lizenz!', 'error') + end +end) + +-- EVENT HANDLER: Manuelle Lizenz ausstellen +RegisterNetEvent('license-system:server:issueManualLicense', function(targetId, licenseData) + local src = source + debugPrint("=== Event: issueManualLicense ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') + return + end + + local issuerPlayer = QBCore.Functions.GetPlayer(src) + if not issuerPlayer then + debugPrint("Aussteller nicht gefunden: " .. src) + return + end + + local targetCitizenId = targetPlayer.PlayerData.citizenid + local issuerCitizenId = issuerPlayer.PlayerData.citizenid + + -- Check if license type is valid + if not Config.LicenseTypes[licenseData.license_type] then + TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error') + return + end + + -- Save photo if provided + if licenseData.photo_url then + -- Here you would save the photo to your storage system + -- For example, to a folder or database + -- This is just a placeholder + debugPrint("Foto für Lizenz vorhanden") + end + + -- First deactivate any existing licenses of this type + local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" + MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type}) + + -- Insert into database with manual data + local query = [[ + INSERT INTO player_licenses + (citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, photo_url, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?) + ]] + + local createdAt = os.time() + + local insertData = { + targetCitizenId, + licenseData.license_type, + licenseData.name, + licenseData.birthday, + licenseData.gender, + licenseData.issue_date, + licenseData.expire_date, + issuerCitizenId, + licenseData.photo_url or '', + createdAt + } + + local result = MySQL.insert.await(query, insertData) + + if result then + -- Invalidate cache + invalidateCache(targetCitizenId) + + local targetName = getPlayerName(targetId) + local issuerName = getPlayerName(src) + local config = Config.LicenseTypes[licenseData.license_type] + + -- Notifications + TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') + TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') + + -- Events + TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseData.license_type) + TriggerClientEvent('license-system:client:refreshMenu', src) + + -- Log + debugPrint("Lizenz " .. licenseData.license_type .. " manuell ausgestellt von " .. issuerName .. " für " .. targetName) + else + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') + end +end) + +-- EVENT HANDLER: Alle Lizenzen eines Spielers anfordern (inkl. inaktive) +RegisterNetEvent('license-system:server:requestAllPlayerLicenses', function(targetId, includeInactive) + local src = source + debugPrint("=== Event: requestAllPlayerLicenses ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", IncludeInactive: " .. tostring(includeInactive)) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") + return + end + + local citizenid = targetPlayer.PlayerData.citizenid + local targetName = getPlayerName(targetId) + + -- Query to get all licenses, including inactive ones if requested + local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" + + local result = MySQL.query.await(query, {citizenid}) + + if not result or #result == 0 then + debugPrint("Keine Lizenzen gefunden für: " .. citizenid) + TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, targetName) + return + end + + -- Process licenses + local licenses = {} + local seenTypes = {} + + for _, license in ipairs(result) do + -- If includeInactive is true, add all licenses + -- Otherwise, only add the newest license per type + local shouldAdd = includeInactive or not seenTypes[license.license_type] + if shouldAdd then + seenTypes[license.license_type] = true + + -- Add holder name + license.holder_name = targetName + + -- Add issuer name + if license.issued_by then + local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local issuerResult = MySQL.query.await(issuerQuery, {license.issued_by}) + + if issuerResult and #issuerResult > 0 then + license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) + else + license.issued_by_name = "System" + end + else + license.issued_by_name = "System" + end + + -- Parse classes + if license.classes then + local success, classes = pcall(json.decode, license.classes) + if success and type(classes) == "table" then + license.classes = classes + else + license.classes = {} + end + else + license.classes = {} + end + + -- Normalize is_active + if license.is_active == nil then + license.is_active = 1 + end + + table.insert(licenses, license) + end + end + + debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) + TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, licenses, targetId, targetName) +end) + -- EXPORT FUNKTIONEN (KORRIGIERT) exports('hasLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end @@ -984,379 +1241,6 @@ function table.count(t) return count end --- Add to server/main.lua -RegisterNetEvent('license-system:server:reactivateLicense', function(targetId, licenseType) - local src = source - debugPrint("=== Event: reactivateLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) - - -- Check if player has permission to reactivate this license type - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - local job = Player.PlayerData.job.name - local canReactivate = false - - -- Check if job can reactivate this license type - if Config.ReactivationPermissions and Config.ReactivationPermissions[job] then - for _, allowedType in ipairs(Config.ReactivationPermissions[job]) do - if allowedType == licenseType then - canReactivate = true - break - end - end - end - - if not canReactivate then - TriggerClientEvent('QBCore:Notify', src, 'Du darfst diesen Lizenztyp nicht reaktivieren!', 'error') - return - end - - -- Get target player - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - - -- Find the most recent license of this type (even if inactive) - local query = [[ - SELECT * FROM player_licenses - WHERE citizenid = ? AND license_type = ? - ORDER BY created_at DESC - LIMIT 1 - ]] - - local result = MySQL.query.await(query, {targetCitizenId, licenseType}) - - if not result or #result == 0 then - TriggerClientEvent('QBCore:Notify', src, 'Keine Lizenz dieses Typs gefunden!', 'error') - return - end - - -- Reactivate the license - local updateQuery = "UPDATE player_licenses SET is_active = 1 WHERE id = ?" - local success = MySQL.update.await(updateQuery, {result[1].id}) - - if success then - -- Invalidate cache - invalidateCache(targetCitizenId, licenseType) - - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - local config = Config.LicenseTypes[licenseType] - - -- Notifications - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich reaktiviert für ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (config.label or licenseType) .. ' wurde reaktiviert!', 'success') - - -- Events - TriggerClientEvent('license-system:client:licenseReactivated', src, targetId, licenseType) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseType .. " reaktiviert von " .. issuerName .. " für " .. targetName) - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Reaktivieren der Lizenz!', 'error') - end -end) - --- Add to client/main.lua --- Add a reactivation option to the player license menu -local function openReactivateLicenseMenu(targetId, targetName) - debugPrint("Öffne Lizenz-Reaktivierungs-Menü für: " .. targetName) - - -- Request all licenses including inactive ones - TriggerServerEvent('license-system:server:requestAllPlayerLicenses', targetId, true) -end - --- New event to receive all licenses including inactive ones -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) - --- Add this option to the player license menu --- In openPlayerLicenseMenu function, add: -table.insert(menuOptions, { - title = 'Lizenz reaktivieren', - description = 'Eine inaktive Lizenz wieder aktivieren', - icon = 'fas fa-redo', - onSelect = function() - openReactivateLicenseMenu(targetId, targetName) - end -}) - --- Add to server/main.lua -RegisterNetEvent('license-system:server:issueManualLicense', function(targetId, licenseData) - local src = source - debugPrint("=== Event: issueManualLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local issuerPlayer = QBCore.Functions.GetPlayer(src) - if not issuerPlayer then - debugPrint("Aussteller nicht gefunden: " .. src) - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - local issuerCitizenId = issuerPlayer.PlayerData.citizenid - - -- Check if license type is valid - if not Config.LicenseTypes[licenseData.license_type] then - TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error') - return - end - - -- Save photo if provided - if licenseData.photo_url then - -- Here you would save the photo to your storage system - -- For example, to a folder or database - -- This is just a placeholder - debugPrint("Foto für Lizenz vorhanden") - end - - -- Insert into database with manual data - local query = [[ - INSERT INTO player_licenses - (citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, photo_url, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?) - ]] - - local createdAt = os.time() - - local insertData = { - targetCitizenId, - licenseData.license_type, - licenseData.name, - licenseData.birthday, - licenseData.gender, - licenseData.issue_date, - licenseData.expire_date, - issuerCitizenId, - licenseData.photo_url or '', - createdAt - } - - -- First deactivate any existing licenses of this type - local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" - MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type}) - - -- Then insert the new license - local result = MySQL.insert.await(query, insertData) - - if result then - -- Invalidate cache - invalidateCache(targetCitizenId) - - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - local config = Config.LicenseTypes[licenseData.license_type] - - -- Notifications - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') - - -- Events - TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseData.license_type) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseData.license_type .. " manuell ausgestellt von " .. issuerName .. " für " .. targetName) - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') - end -end) - --- Add a new event to get all licenses including inactive ones -RegisterNetEvent('license-system:server:requestAllPlayerLicenses', function(targetId, includeInactive) - local src = source - debugPrint("=== Event: requestAllPlayerLicenses ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", IncludeInactive: " .. tostring(includeInactive)) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local citizenid = targetPlayer.PlayerData.citizenid - local targetName = getPlayerName(targetId) - - -- Query to get all licenses, including inactive ones if requested - local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" - - local result = MySQL.query.await(query, {citizenid}) - - if not result or #result == 0 then - debugPrint("Keine Lizenzen gefunden für: " .. citizenid) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, targetName) - return - end - - -- Process licenses - local licenses = {} - local seenTypes = {} - - for _, license in ipairs(result) do - -- If includeInactive is true, add all licenses - -- Otherwise, only add the newest license per type - local shouldAdd = includeInactive or not seenTypes[license.license_type] - - if shouldAdd then - seenTypes[license.license_type] = true - - -- Add holder name - license.holder_name = targetName - - -- Add issuer name - if license.issued_by then - local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local issuerResult = MySQL.query.await(issuerQuery, {license.issued_by}) - - if issuerResult and #issuerResult > 0 then - license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) - else - license.issued_by_name = "System" - end - else - license.issued_by_name = "System" - end - - -- Parse classes - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - license.classes = {} - end - - -- Normalize is_active - if license.is_active == nil then - license.is_active = 1 - end - - table.insert(licenses, license) - end - end - - debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, licenses, targetId, targetName) -end) - - debugPrint("License-System Server vollständig geladen (Status-Fix)") + +