diff --git a/data/plugin.log b/data/plugin.log index 72b9092a..bf45f5d5 100755 --- a/data/plugin.log +++ b/data/plugin.log @@ -11708,3 +11708,32 @@ EJECUTANDO PATCH - Se detectaron cambios Response Patch CallBell: {"contact":{"uuid":"74cc2bb45eb8409f92cd5dba99200d26","name":"Juan Escutia","phoneNumber":"5214181878106","avatarUrl":null,"createdAt":"2024-01-08T17:04:13Z","closedAt":"2026-02-01T15:22:57Z","source":"whatsapp","funnelId":null,"href":"https://dash.callbell.eu/contacts/74cc2bb45eb8409f92cd5dba99200d26","conversationHref":"https://dash.callbell.eu/chat/53c8229c428c4081b197ab136feab73b","tags":["s0LOCS","PRUEBAS"],"assignedUser":null,"customFields":{"user entry point":"inbound_message","Clabe Interbancaria":"124180650741646979","Estado":"🟢 Activo ","user name":"Daniel Humberto","Monto Ultimo Pago":"💲 100","Domicilio":"📍 Calle San Antonio 13, Lindavista, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., México","Cliente":"135","Site":"El Lindero","URL":"🌐 https://sistema.siip.mx/crm/client/135","Saldo Actual":"💲10375 a favor","Nombre":"👤 Juan Escutia","password-antena":"{\"Servicio 1\":\"Aq{0J8:n [TzP9TC3NS2Rm@Dzy] +[2026-02-18 23:40:43.332156] [debug] Intentando PATCH en clients/167 para atributo ID: 17 con valor: TzP9TC3NS2Rm@Dzy +[2026-02-18 23:40:43.773077] [notice] Logging level set to:debug +[2026-02-18 23:40:43.773202] [debug] Payload recibido: {"uuid":"fa0b2295-c214-411d-8ef7-1bad1919790b","changeType":"edit","entity":"client","entityId":"167","eventName":"client.edit","extraData":{"entity":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"TV AZTECA","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true},{"id":283,"clientId":167,"customAttributeId":21,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","value":"GUSANO","clientZoneVisible":true},{"id":413,"clientId":167,"customAttributeId":17,"name":"Password Antena Cliente","key":"passwordAntenaCliente","value":"TzP9TC3NS2Rm@Dzy","clientZoneVisible":false}],"accountBalance":750,"accountCredit":750,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"TV AZTECA","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true},{"id":283,"clientId":167,"customAttributeId":21,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","value":"GUSANO","clientZoneVisible":true}],"accountBalance":750,"accountCredit":750,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}} + +[2026-02-18 23:40:43.890788] [debug] Evento recibido: client.edit +[2026-02-18 23:40:43.891062] [info] Procesando evento client.edit para entityId: 167 +[2026-02-18 23:40:43.891163] [debug] Payload completo client.edit: {"uuid":"fa0b2295-c214-411d-8ef7-1bad1919790b","changeType":"edit","entity":"client","entityId":"167","eventName":"client.edit","extraData":{"entity":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"TV AZTECA","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true},{"id":283,"clientId":167,"customAttributeId":21,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","value":"GUSANO","clientZoneVisible":true},{"id":413,"clientId":167,"customAttributeId":17,"name":"Password Antena Cliente","key":"passwordAntenaCliente","value":"TzP9TC3NS2Rm@Dzy","clientZoneVisible":false}],"accountBalance":750,"accountCredit":750,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"TV AZTECA","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true},{"id":283,"clientId":167,"customAttributeId":21,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","value":"GUSANO","clientZoneVisible":true}],"accountBalance":750,"accountCredit":750,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}} +[2026-02-18 23:40:43.891218] [info] Llamando a updatePasswordAntenaIfNeeded para cliente: 167 +[2026-02-18 23:40:43.891258] [info] Iniciando verificación/sincronización de contraseña para el cliente ID: 167 diff --git a/scripts-uisp/audit_client_passwords.php b/scripts-uisp/audit_client_passwords.php index 4df540ce..b251a14a 100644 --- a/scripts-uisp/audit_client_passwords.php +++ b/scripts-uisp/audit_client_passwords.php @@ -32,10 +32,13 @@ if ($fixOption === 'all') { // -- Helpers -- function logMessage($message) { - global $logFile; + $logFile = __DIR__ . '/audit_passwords.log'; $timestamp = date('Y-m-d H:i:s'); file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND); - fwrite(STDERR, "[$timestamp] $message\n"); + // Only write to STDERR if running in CLI mode, not web server (optional, but good practice) + if (php_sapi_name() === 'cli') { + fwrite(STDERR, "[$timestamp] $message\n"); + } } function isTestEnvironment($config) @@ -407,268 +410,277 @@ function patchClientAttributes($ucrmApi, $clientId, array $attributeUpdates) } -// 0. Resolve Attribute IDs -$customAttributeId = null; -$siteAttributeId = null; -$antenaSectorialAttributeId = null; -try { - $attributes = $ucrmApi->get('custom-attributes', ['attributeType' => 'client']); - foreach ($attributes as $attr) { - match ($attr['key']) { - $customAttributeKey => $customAttributeId = $attr['id'], - $siteAttributeKey => $siteAttributeId = $attr['id'], - $antenaSectorialAttributeKey => $antenaSectorialAttributeId = $attr['id'], - default => null, - }; +/** + * Resolves UCRM custom attribute IDs based on keys. + */ +function resolveAttributeIds($ucrmApi, $customAttributeKey, $siteAttributeKey, $antenaSectorialAttributeKey) +{ + $customAttributeId = null; + $siteAttributeId = null; + $antenaSectorialAttributeId = null; + + try { + $attributes = $ucrmApi->get('custom-attributes', ['attributeType' => 'client']); + foreach ($attributes as $attr) { + match ($attr['key']) { + $customAttributeKey => $customAttributeId = $attr['id'], + $siteAttributeKey => $siteAttributeId = $attr['id'], + $antenaSectorialAttributeKey => $antenaSectorialAttributeId = $attr['id'], + default => null, + }; + } + } catch (\Exception $e) { + logMessage("Error fetching attributes: " . $e->getMessage()); + return null; // Return null on failure } -} catch (\Exception $e) { - logMessage("Error fetching attributes: " . $e->getMessage()); - exit(1); + + if (!$customAttributeId) { + logMessage("Error: Custom attribute '$customAttributeKey' not found."); + return null; + } + + // Log resolved IDs + logMessage("Resolved attribute IDs: password=$customAttributeId, site=" . ($siteAttributeId ?? 'N/A') . ", antenaSectorial=" . ($antenaSectorialAttributeId ?? 'N/A')); + + return [ + 'password' => $customAttributeId, + 'site' => $siteAttributeId, + 'antenaSectorial' => $antenaSectorialAttributeId, + ]; } -if (!$customAttributeId) { - logMessage("Error: Custom attribute '$customAttributeKey' not found."); - exit(1); -} - -// Log resolved IDs -logMessage("Resolved attribute IDs: password=$customAttributeId, site=" . ($siteAttributeId ?? 'N/A') . ", antenaSectorial=" . ($antenaSectorialAttributeId ?? 'N/A')); - -// Build attribute IDs map for fixClientData -$attributeIds = [ - 'password' => $customAttributeId, - 'site' => $siteAttributeId, - 'antenaSectorial' => $antenaSectorialAttributeId, -]; - // -- Main Execution -- -if ($fixOption === null) { - // Check for --file argument - $fileOption = null; - foreach ($argv as $arg) { - if (strpos($arg, '--file=') === 0) { - $fileOption = substr($arg, 7); - } +if (!defined('INCLUDED_AS_LIBRARY')) { + // Initialization + $config = PluginConfigManager::create()->loadConfig(); + $ipServer = $config['ipserver'] ?? 'localhost'; + $apiUrl = "https://$ipServer/crm/api/v1.0/"; + $token = $config['apitoken'] ?? ''; + + if (empty($token)) { + logMessage("Error: API Token is missing in plugin configuration."); + exit(1); } - if ($fileOption) { - if ($fileOption[0] !== '/') { - global $initialDir; - $fileOption = $initialDir . '/' . $fileOption; - } + // Initialize UCRM Client + $client = new Client([ + 'base_uri' => $apiUrl, + 'verify' => false, + 'headers' => [ + 'X-Auth-App-Key' => $token, + 'Content-Type' => 'application/json', + ], + ]); + $ucrmApi = new UcrmApi($client, $token); - if (!file_exists($fileOption)) { - logMessage("Error: File not found: $fileOption"); - exit(1); - } + // Initialize UNMS Client + $unmsClient = new Client([ + 'base_uri' => "https://{$ipServer}/nms/api/v2.1/", + 'verify' => false, + 'headers' => [ + 'X-Auth-Token' => $config['unmsApiToken'] ?? '' + ] + ]); - logMessage("Processing from file: $fileOption"); + // Resolve IDs + $attributeIds = resolveAttributeIds($ucrmApi, $customAttributeKey, $siteAttributeKey, $antenaSectorialAttributeKey); + if (!$attributeIds) { + exit(1); + } - // Read all lines - $lines = file($fileOption, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - - // Process lines - // We need to loop, process, remove, rewrite. - // Ideally, we process the list in memory and rewrite the file after EACH success to satisfy "removing it as we go". - - $originalCount = count($lines); - $processedCount = 0; - - // Helper to rewrite file - $rewriteFile = function ($remainingLines) use ($fileOption) { - file_put_contents($fileOption, implode("\n", $remainingLines) . "\n"); - }; - - // We allow a header row if it starts with "clientId" or "id" - // If we remove the header, we lose it for subsequent runs? No, we should probably keep header if present? - // Or just assume data. - // Let's assume the user might have a header. - // If the first line is non-numeric, treat as header? - - $header = null; - if (!empty($lines)) { - $firstLine = $lines[0]; - $parts = explode(',', $firstLine); - if (!is_numeric($parts[0])) { - $header = array_shift($lines); // Remove header from processing list + if ($fixOption === null) { + // Check for --file argument + $fileOption = null; + foreach ($argv as $arg) { + if (strpos($arg, '--file=') === 0) { + $fileOption = substr($arg, 7); } } - $queue = $lines; // Working copy - - while (!empty($queue)) { - $line = array_shift($queue); // Take next - if (empty(trim($line))) continue; - - $clientId = trim($line); - - if (!is_numeric($clientId)) { - logMessage("Skipping invalid line: $line"); - continue; + if ($fileOption) { + if ($fileOption[0] !== '/') { + $fileOption = $initialDir . '/' . $fileOption; } - // Process Client - $clientId = (int)$clientId; - logMessage("Processing ID from file: $clientId"); + if (!file_exists($fileOption)) { + logMessage("Error: File not found: $fileOption"); + exit(1); + } - // Use fetchAllClients filtered by ID? No, too slow. - // Directly use fixClientPassword which fetches services inside. - // BUT we need the current password to decide if we fix? - // `fixClientPassword` takes `$currentPassword`. - // We can fetch the specific client first. + logMessage("Processing from file: $fileOption"); - try { - $clientDataResponse = $ucrmApi->get("clients/$clientId"); - $currentAttributes = $clientDataResponse['attributes'] ?? []; - $currentPass = null; - foreach ($currentAttributes as $attr) { - if ($attr['key'] === $customAttributeKey) { - $currentPass = $attr['value']; - break; - } + // Read all lines + $lines = file($fileOption, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + // Process lines + $originalCount = count($lines); + $processedCount = 0; + + // Helper to rewrite file + $rewriteFile = function ($remainingLines) use ($fileOption) { + file_put_contents($fileOption, implode("\n", $remainingLines) . "\n"); + }; + + $header = null; + if (!empty($lines)) { + $firstLine = $lines[0]; + $parts = explode(',', $firstLine); + if (!is_numeric($parts[0])) { + $header = array_shift($lines); // Remove header from processing list + } + } + + $queue = $lines; // Working copy + + while (!empty($queue)) { + $line = array_shift($queue); // Take next + if (empty(trim($line))) continue; + + $clientId = trim($line); + + if (!is_numeric($clientId)) { + logMessage("Skipping invalid line: $line"); + continue; } - // Determine if valid (same logic as audit) - // Actually, if it's in the list, we assume it's invalid? - // Or should we re-validate? safer to re-validate slightly to get current state. + $clientId = (int)$clientId; + logMessage("Processing ID from file: $clientId"); - $fixResult = fixClientData($clientId, $ucrmApi, $unmsClient, $attributeIds, $config, $currentPass); - $newPass = $fixResult['password']; - $siteInfo = $fixResult['siteName'] ? " | Site: {$fixResult['siteName']}" : ''; - $devInfo = $fixResult['deviceInfo'] ? " | Antena: {$fixResult['deviceInfo']}" : ''; - logMessage("Client $clientId result: $newPass$siteInfo$devInfo"); - } catch (\Exception $e) { - logMessage("Error processing Client $clientId: " . $e->getMessage()); - // Do we remove it if error? - // User said "que cada que procese un id lo quite". - // Usually specific errors might warrant keeping, but if we want to "resume", maybe we remove logic? - // Let's assuming "Processed" means attempted. + try { + $clientDataResponse = $ucrmApi->get("clients/$clientId"); + $currentAttributes = $clientDataResponse['attributes'] ?? []; + $currentPass = null; + foreach ($currentAttributes as $attr) { + if ($attr['key'] === $customAttributeKey) { + $currentPass = $attr['value']; + break; + } + } + + $fixResult = fixClientData($clientId, $ucrmApi, $unmsClient, $attributeIds, $config, $currentPass); + $newPass = $fixResult['password']; + $siteInfo = $fixResult['siteName'] ? " | Site: {$fixResult['siteName']}" : ''; + $devInfo = $fixResult['deviceInfo'] ? " | Antena: {$fixResult['deviceInfo']}" : ''; + logMessage("Client $clientId result: $newPass$siteInfo$devInfo"); + } catch (\Exception $e) { + logMessage("Error processing Client $clientId: " . $e->getMessage()); + } + + // Rewrite file + $validLines = $queue; + if ($header) { + array_unshift($validLines, $header); + } + $rewriteFile($validLines); } - // Rewrite file - // Content = Header (if any) + Remaining Queue - $validLines = $queue; - if ($header) { - array_unshift($validLines, $header); - } - $rewriteFile($validLines); - - // Sleep slightly to avoid hammering? - // usleep(100000); - } - - logMessage("File processing complete."); - exit(0); - } -} - -if ($fixLimit > 0) { - logMessage("Fix mode enabled. Limit: " . ($fixOption === 'all' ? 'ALL' : $fixLimit)); -} - - -// 1. Fetch all clients -logMessage("Fetching clients..."); -$clients = fetchAllClients($client); -logMessage("Total active clients fetched: " . count($clients)); - -// 2. Filter & Fix -$results = []; -$fixedCount = 0; - -foreach ($clients as $clientData) { - $password = null; - $attributes = $clientData['attributes'] ?? []; - - foreach ($attributes as $attr) { - if ($attr['key'] === $customAttributeKey) { - $password = $attr['value']; - break; + logMessage("File processing complete."); + exit(0); } } - // Validation Logic - $isValid = true; - $reason = ""; + if ($fixLimit > 0) { + logMessage("Fix mode enabled. Limit: " . ($fixOption === 'all' ? 'ALL' : $fixLimit)); + } - // Strict validation for "invalid" detection to trigger fix, - // BUT we need to be careful not to flag "valid" generated strings (like "Servicio 1: ...") as invalid if we just want to audit "missing". - // Facade generates complex strings. If we flag them all as invalid because of spaces, we will loop forever fixing them (if generating same structure). - // Logic: If it contains "Servicio" or "⚠️", it is "Managed". - // Only flag if Empty OR (Length check fail AND Not Managed). - // Detect explicit error states that should be retried (e.g. "⚠️ Error Regenerating...") - $isError = (stripos((string)$password, 'Error') !== false); + // 1. Fetch all clients + logMessage("Fetching clients..."); + $clients = fetchAllClients($client); + logMessage("Total active clients fetched: " . count($clients)); - $isManaged = (strpos((string)$password, 'Servicio') !== false || strpos((string)$password, '⚠️') !== false); + // 2. Filter & Fix + $results = []; + $fixedCount = 0; - if (empty($password)) { - $isValid = false; - $reason = "Missing"; - } elseif ($isError) { - $isValid = false; - $reason = "Error (Retry)"; - } elseif (!$isManaged) { - $len = strlen($password); - if ($len < 8 || $len > 20) { + foreach ($clients as $clientData) { + $password = null; + $attributes = $clientData['attributes'] ?? []; + + foreach ($attributes as $attr) { + if ($attr['key'] === $customAttributeKey) { + $password = $attr['value']; + break; + } + } + + // Validation Logic + $isValid = true; + $reason = ""; + + // Detect explicit error states that should be retried (e.g. "⚠️ Error Regenerating...") + $isError = (stripos((string)$password, 'Error') !== false); + + $isManaged = (strpos((string)$password, 'Servicio') !== false || strpos((string)$password, '⚠️') !== false); + + if (empty($password)) { $isValid = false; - $reason = "Length ($len)"; - } elseif (strpos($password, ' ') !== false) { + $reason = "Missing"; + } elseif ($isError) { $isValid = false; - $reason = "Spaces"; - } - } - - - if (!$isValid) { - $newPassword = ""; - $actionTaken = "None"; - - // Fix Logic - if ($fixLimit > 0 && $fixedCount < $fixLimit) { - logMessage("Fixing client {$clientData['id']}..."); - $fixResult = fixClientData($clientData['id'], $ucrmApi, $unmsClient, $attributeIds, $config, $password); - $newPassword = $fixResult['password']; - - // Basic check if fix resulted in a change/value - if ($fixResult['changed'] ?? false) { - $actionTaken = "Updated"; - $fixedCount++; - } else { - $actionTaken = "Attempted (No Change/Error)"; + $reason = "Error (Retry)"; + } elseif (!$isManaged) { + $len = strlen($password); + if ($len < 8 || $len > 20) { + $isValid = false; + $reason = "Length ($len)"; + } elseif (strpos($password, ' ') !== false) { + $isValid = false; + $reason = "Spaces"; } } - $results[] = [ - 'clientId' => $clientData['id'], - 'name' => ($clientData['firstName'] ?? '') . ' ' . ($clientData['lastName'] ?? ''), - 'userIdent' => $clientData['userIdent'] ?? '', - 'currentPassword' => $password ?? '', - 'action' => $actionTaken, - 'newPassword' => $newPassword - ]; + + if (!$isValid) { + $newPassword = ""; + $actionTaken = "None"; + + // Fix Logic + if ($fixLimit > 0 && $fixedCount < $fixLimit) { + logMessage("Fixing client {$clientData['id']}..."); + $fixResult = fixClientData($clientData['id'], $ucrmApi, $unmsClient, $attributeIds, $config, $password); + $newPassword = $fixResult['password']; + + // Basic check if fix resulted in a change/value + if ($fixResult['changed'] ?? false) { + $actionTaken = "Updated"; + $fixedCount++; + } else { + $actionTaken = "Attempted (No Change/Error)"; + } + } + + $results[] = [ + 'clientId' => $clientData['id'], + 'name' => ($clientData['firstName'] ?? '') . ' ' . ($clientData['lastName'] ?? ''), + 'userIdent' => $clientData['userIdent'] ?? '', + 'currentPassword' => $password ?? '', + 'action' => $actionTaken, + 'newPassword' => $newPassword + ]; + } } + + // 3. Output CSV + logMessage("Found " . count($results) . " clients with invalid passwords."); + if ($fixLimit > 0) { + logMessage("Fixed $fixedCount clients."); + } + + echo "clientId,name,userIdent,currentPassword,action,newPassword\n"; + + foreach ($results as $row) { + echo sprintf( + "%s,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", + $row['clientId'], + str_replace('"', '""', $row['name']), + str_replace('"', '""', $row['userIdent']), + str_replace('"', '""', $row['currentPassword']), + $row['action'], + str_replace('"', '""', $row['newPassword']) + ); + } + + logMessage("Done."); } - -// 3. Output CSV -logMessage("Found " . count($results) . " clients with invalid passwords."); -if ($fixLimit > 0) { - logMessage("Fixed $fixedCount clients."); -} - -echo "clientId,name,userIdent,currentPassword,action,newPassword\n"; - -foreach ($results as $row) { - echo sprintf( - "%s,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", - $row['clientId'], - str_replace('"', '""', $row['name']), - str_replace('"', '""', $row['userIdent']), - str_replace('"', '""', $row['currentPassword']), - $row['action'], - str_replace('"', '""', $row['newPassword']) - ); -} - -logMessage("Done."); diff --git a/src/Facade/PluginNotifierFacade.php b/src/Facade/PluginNotifierFacade.php index 3e465311..94bd5547 100755 --- a/src/Facade/PluginNotifierFacade.php +++ b/src/Facade/PluginNotifierFacade.php @@ -16,6 +16,8 @@ use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Exception\GuzzleException; +use Ubnt\UcrmPluginSdk\Service\PluginConfigManager; + class PluginNotifierFacade extends AbstractStripeOperationsFacade { /** @@ -49,6 +51,83 @@ class PluginNotifierFacade extends AbstractStripeOperationsFacade $this->getVaultCredentialsByClientId($clientId); } + /** + * Procesar la lógica de corrección de contraseña cuando se detecta la etiqueta "OBTENER PASSWORD ANTENA". + * Reutiliza la lógica del script audit_client_passwords.php. + */ + public function processClientPasswordAntenna(int $clientId, array $clientEntity): void + { + $this->logger->info("Procesando etiqueta OBTENER PASSWORD ANTENA para cliente $clientId"); + + // 1. Define constant to prevent script execution when including + if (!defined('INCLUDED_AS_LIBRARY')) { + define('INCLUDED_AS_LIBRARY', true); + } + + // 2. Include the script functions + $scriptPath = __DIR__ . '/../../scripts-uisp/audit_client_passwords.php'; + if (file_exists($scriptPath)) { + require_once $scriptPath; + } else { + $this->logger->error("No se encontró el script de auditoría en: $scriptPath"); + return; + } + + // 3. Initialize Config & UNMS Client + // Note: $this->ucrmApi is already available from parent class. + $ucrmConfig = PluginConfigManager::create()->loadConfig(); + $ipServer = $ucrmConfig['ipserver'] ?? 'localhost'; + + $unmsToken = $ucrmConfig['unmsApiToken'] ?? $this->pluginData->unmsAppToken ?? ''; + + if (empty($unmsToken)) { + $this->logger->error("UNMS API Token is missing. Cannot proceed with password fix."); + return; + } + + $unmsClientInstance = new GuzzleClient([ + 'base_uri' => "https://{$ipServer}/nms/api/v2.1/", + 'verify' => false, + 'headers' => [ + 'X-Auth-Token' => $unmsToken + ] + ]); + + // 4. Resolve Attributes + // Use global function from script + $attributeIds = \resolveAttributeIds($this->ucrmApi, 'passwordAntenaCliente', 'site', 'antenaSectorial'); + if (!$attributeIds) { + $this->logger->error("Could not resolve attributes for password fix."); + return; + } + + // 5. Get current password + $currentPassword = null; + foreach ($clientEntity['attributes'] ?? [] as $attr) { + if ($attr['key'] === 'passwordAntenaCliente') { + $currentPassword = $attr['value']; + break; + } + } + + // 6. Fix Data + try { + $fixResult = \fixClientData($clientId, $this->ucrmApi, $unmsClientInstance, $attributeIds, $ucrmConfig, $currentPassword); + $this->logger->info("Password fix result for client $clientId: " . json_encode($fixResult)); + } catch (\Exception $e) { + $this->logger->error("Error executing fixClientData: " . $e->getMessage()); + } + + // 7. Remove Tag + // Reuse parent method + $this->removeTagFromClient($clientId, 'OBTENER PASSWORD ANTENA'); + } + + + /* + * Implementation of abstract method from AbstractStripeOperationsFacade + */ + /* * Implementation of abstract method from AbstractStripeOperationsFacade */ diff --git a/src/Plugin.php b/src/Plugin.php index 0f31632b..7e3ccc5c 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -455,17 +455,22 @@ class Plugin $clabeTagExistsBefore = false; $stripeTagExistsBefore = false; + $passwordAntenaTagExistsBefore = false; + $clabeTagExists = false; $stripeTagExists = false; + $passwordAntenaTagExists = false; foreach ($tagsBefore as $tag) { if ($tag['name'] === 'CREAR CLABE STRIPE') $clabeTagExistsBefore = true; if ($tag['name'] === 'CREAR CLIENTE STRIPE') $stripeTagExistsBefore = true; + if ($tag['name'] === 'OBTENER PASSWORD ANTENA') $passwordAntenaTagExistsBefore = true; } foreach ($tags as $tag) { if ($tag['name'] === 'CREAR CLABE STRIPE') $clabeTagExists = true; if ($tag['name'] === 'CREAR CLIENTE STRIPE') $stripeTagExists = true; + if ($tag['name'] === 'OBTENER PASSWORD ANTENA') $passwordAntenaTagExists = true; } if ($clabeTagExists && !$clabeTagExistsBefore) { @@ -477,6 +482,11 @@ class Plugin $this->logger->debug('La etiqueta CREAR CLIENTE STRIPE se agregó al cliente'); $this->pluginNotifierFacade->createStripeClient($notification, 'CREAR CLIENTE STRIPE', false); } + + if ($passwordAntenaTagExists && !$passwordAntenaTagExistsBefore) { + $this->logger->debug('La etiqueta OBTENER PASSWORD ANTENA se agregó al cliente'); + $this->pluginNotifierFacade->processClientPasswordAntenna($clientID, $entity); + } } // Automatización: Sincronizar cambios de Nombre o Email con Stripe