* @copyright Copyright (c) 2024 Kevin Feiler / AVVGO * @license MIT License * @link https://avvgo.de * @version 2.0.0 */ if (!defined("WHMCS")) { die("This file cannot be accessed directly"); } use WHMCS\Database\Capsule; function keyhelpmanager_MetaData() { return [ "DisplayName" => "KeyHelp Manager", "APIVersion" => "1.1", "RequiresServer" => true, "DefaultNonSSLPort" => "80", "DefaultSSLPort" => "443", "ServiceSingleSignOnLabel" => "Login to KeyHelp", "AdminSingleSignOnLabel" => "Login as Admin", ]; } function keyhelpmanager_ConfigOptions() { return [ "template" => [ "FriendlyName" => "KeyHelp Template", "Type" => "dropdown", "Options" => _keyhelpmanager_GetTemplates(), "Description" => "Select a KeyHelp template/plan for this product", "Loader" => "keyhelpmanager_TemplateLoader", ], ]; } function keyhelpmanager_TemplateLoader($params) { return _keyhelpmanager_GetTemplates($params); } function _keyhelpmanager_APIRequest( array $params, string $endpoint, string $method = "GET", array $data = [], ) { try { // Use centralized server configuration from WHMCS server settings $hostname = $params["serverhostname"] ?? ""; $apiKey = $params["serveraccesshash"] ?? ""; $useSSL = $params["serversecure"] ?? "on"; // Check if SSL verification should be disabled // Automatically disable for IP addresses or if serverhttpprefix is set to "no-verify" $httpPrefix = $params["serverhttpprefix"] ?? ""; $isIpAddress = filter_var($hostname, FILTER_VALIDATE_IP) !== false; $verifySSL = $httpPrefix === "no-verify" || $isIpAddress ? "off" : "on"; if (empty($hostname) || empty($apiKey)) { return [ "success" => false, "error" => "KeyHelp server not configured. Please configure the server in WHMCS Setup > Products/Services > Servers", ]; } $protocol = $useSSL === "on" ? "https" : "http"; $url = sprintf("%s://%s/api/v2%s", $protocol, $hostname, $endpoint); // Log warning if SSL verification is disabled if ($verifySSL === "off") { logActivity( "KeyHelp Manager: SSL verification disabled for " . $hostname . ($isIpAddress ? " (IP address detected)" : " (manual override)"), ); } $client = new \GuzzleHttp\Client([ "verify" => $verifySSL === "on", "timeout" => 30, "http_errors" => false, ]); $options = [ "headers" => [ "X-API-Key" => $apiKey, "Content-Type" => "application/json", "Accept" => "application/json", ], ]; if (!empty($data) && in_array($method, ["POST", "PUT", "PATCH"])) { $options["json"] = $data; } $response = $client->request($method, $url, $options); $statusCode = $response->getStatusCode(); $body = (string) $response->getBody(); $responseData = json_decode($body, true); logModuleCall( "keyhelpmanager", $method . " " . $endpoint, $data, $statusCode . " - " . $body, $responseData, [$apiKey], ); if ($statusCode >= 200 && $statusCode < 300) { return [ "success" => true, "data" => $responseData, "status_code" => $statusCode, ]; } $errorMessage = $responseData["message"] ?? ($responseData["error"] ?? sprintf("HTTP %d - %s", $statusCode, $body)); return [ "success" => false, "error" => "KeyHelp API Error: " . $errorMessage, "status_code" => $statusCode, ]; } catch (\GuzzleHttp\Exception\ConnectException $e) { return [ "success" => false, "error" => "Connection failed: " . $e->getMessage(), ]; } catch (\Exception $e) { return [ ' success' => false, "error" => "Unexpected error: " . $e->getMessage(), ]; } } function keyhelpmanager_CreateAccount(array $params) { try { $domain = $params["domain"] ?? ""; $username = $params["username"] ?? _keyhelpmanager_GenerateUsername($domain); $password = $params["password"] ?? _keyhelpmanager_GeneratePassword(); $clientEmail = $params["clientsdetails"]["email"] ?? ""; $clientName = trim( ($params["clientsdetails"]["firstname"] ?? "") . " " . ($params["clientsdetails"]["lastname"] ?? ""), ); if (empty($domain)) { return "Domain is required"; } // Get selected template from config options $templateId = $params["configoption1"] ?? ""; $accountData = [ "login_name" => $username, "password" => $password, "email" => $clientEmail, "display_name" => $clientName, ]; // Use template if selected if (!empty($templateId)) { $accountData["plan_id"] = $templateId; } $result = _keyhelpmanager_APIRequest( $params, "/users", "POST", $accountData, ); if (!$result["success"]) { return $result["error"]; } $userId = $result["data"]["id"] ?? null; if (empty($userId)) { return "User created but no ID returned"; } // Create domain with template settings $domainData = ["domain_name" => $domain, "user_id" => $userId]; if (!empty($templateId)) { $domainData["template_id"] = $templateId; } $domainResult = _keyhelpmanager_APIRequest( $params, "/domains", "POST", $domainData, ); if (!$domainResult["success"]) { _keyhelpmanager_APIRequest($params, "/users/" . $userId, "DELETE"); return "Domain creation failed: " . $domainResult["error"]; } // Save account details including domain ID $domainId = $domainResult["data"]["id"] ?? null; _keyhelpmanager_SaveAccountDetails($params["serviceid"], [ "username" => $username, "password" => $password, "userid" => $userId, "domainid" => $domainId, "template" => $templateId, ]); return "success"; } catch (\Exception $e) { return "Account creation failed: " . $e->getMessage(); } } function keyhelpmanager_SuspendAccount(array $params) { try { $userId = _keyhelpmanager_GetUserId($params); if (empty($userId)) { return "User ID not found"; } $result = _keyhelpmanager_APIRequest( $params, "/users/" . $userId, "PUT", ["is_locked" => true], ); return $result["success"] ? "success" : $result["error"]; } catch (\Exception $e) { return "Suspend failed: " . $e->getMessage(); } } function keyhelpmanager_UnsuspendAccount(array $params) { try { $userId = _keyhelpmanager_GetUserId($params); if (empty($userId)) { return "User ID not found"; } $result = _keyhelpmanager_APIRequest( $params, "/users/" . $userId, "PUT", ["is_locked" => false], ); return $result["success"] ? "success" : $result["error"]; } catch (\Exception $e) { return "Unsuspend failed: " . $e->getMessage(); } } function keyhelpmanager_TerminateAccount(array $params) { try { $userId = _keyhelpmanager_GetUserId($params); if (empty($userId)) { return "User ID not found"; } $result = _keyhelpmanager_APIRequest( $params, "/users/" . $userId, "DELETE", ); if ($result["success"]) { _keyhelpmanager_DeleteAccountDetails($params["serviceid"]); return "success"; } return $result["error"]; } catch (\Exception $e) { return "Termination failed: " . $e->getMessage(); } } function keyhelpmanager_ClientArea(array $params) { try { $accountDetails = _keyhelpmanager_GetAccountDetails( $params["serviceid"], ); $userId = $accountDetails["userid"] ?? null; $domainId = $accountDetails["domainid"] ?? null; $templateId = $accountDetails["template"] ?? null; $hostname = $params["serverhostname"] ?? ""; $useSSL = $params["serversecure"] ?? "on"; $protocol = $useSSL === "on" ? "https" : "http"; $loginUrl = sprintf("%s://%s", $protocol, $hostname); // Direct panel link $panelUrl = sprintf( "%s://%s/admin/index.php?p=domains&action=edit&id=%s", $protocol, $hostname, $domainId ?? "", ); $templateVars = [ "login_url" => $loginUrl, "panel_url" => $panelUrl, "username" => $accountDetails["username"] ?? ($params["username"] ?? "N/A"), "password" => $accountDetails["password"] ?? ($params["password"] ?? "••••••••"), "domain" => $params["domain"] ?? "N/A", "template_id" => $templateId, "template_name" => "", "stats" => null, "error" => null, ]; // Get template name if template ID exists if (!empty($templateId)) { $templateResult = _keyhelpmanager_APIRequest( $params, "/plans/" . $templateId, "GET", ); if ($templateResult["success"] && isset($templateResult["data"])) { $templateVars["template_name"] = $templateResult["data"]["name"] ?? "Template " . $templateId; } } if (!empty($userId)) { $statsResult = _keyhelpmanager_APIRequest( $params, "/users/" . $userId . "/statistics", "GET", ); if ($statsResult["success"] && isset($statsResult["data"])) { $stats = $statsResult["data"]; $templateVars["stats"] = [ "disk_used" => $stats["disk_usage"] ?? 0, "disk_limit" => $stats["disk_limit"] ?? 0, "disk_used_formatted" => _keyhelpmanager_FormatBytes( $stats["disk_usage"] ?? 0, ), "disk_limit_formatted" => _keyhelpmanager_FormatBytes( $stats["disk_limit"] ?? 0, ), "disk_percent" => _keyhelpmanager_CalculatePercent( $stats["disk_usage"] ?? 0, $stats["disk_limit"] ?? 0, ), "bandwidth_used" => $stats["traffic_usage"] ?? 0, "bandwidth_limit" => $stats["traffic_limit"] ?? 0, "bandwidth_used_formatted" => _keyhelpmanager_FormatBytes( $stats["traffic_usage"] ?? 0, ), "bandwidth_limit_formatted" => _keyhelpmanager_FormatBytes( $stats["traffic_limit"] ?? 0, ), "bandwidth_percent" => _keyhelpmanager_CalculatePercent( $stats["traffic_usage"] ?? 0, $stats["traffic_limit"] ?? 0, ), "domains" => $stats["domains_count"] ?? 0, "databases" => $stats["databases_count"] ?? 0, "email_accounts" => $stats["email_accounts_count"] ?? 0, ]; } } return ["templatefile" => "clientarea", "vars" => $templateVars]; } catch (\Exception $e) { return [ "templatefile" => "clientarea", "vars" => [ "error" => "Failed to load account information: " . $e->getMessage(), ], ]; } } function keyhelpmanager_LoginLink(array $params) { try { $accountDetails = _keyhelpmanager_GetAccountDetails( $params["serviceid"], ); $userId = $accountDetails["userid"] ?? null; if (empty($userId)) { return ["success" => false, "errorMsg" => "User ID not found"]; } $sessionData = ["user_id" => $userId, "lifetime" => 300]; $result = _keyhelpmanager_APIRequest( $params, "/sessions", "POST", $sessionData, ); if (!$result["success"]) { return [ "success" => false, "errorMsg" => "Session creation failed: " . $result["error"], ]; } $sessionToken = $result["data"]["token"] ?? null; if (empty($sessionToken)) { return [ "success" => false, "errorMsg" => "No session token received", ]; } $hostname = $params["serverhostname"] ?? ($params["configoption1"] ?? ""); $useSSL = $params["configoption3"] ?? "on"; $protocol = $useSSL === "on" ? "https" : "http"; $loginUrl = sprintf( "%s://%s/login?token=%s", $protocol, $hostname, $sessionToken, ); return ["success" => true, "redirectTo" => $loginUrl]; } catch (\Exception $e) { return [ "success" => false, "errorMsg" => "Login link creation failed: " . $e->getMessage(), ]; } } function keyhelpmanager_ChangePassword(array $params) { try { $userId = _keyhelpmanager_GetUserId($params); $newPassword = $params["password"] ?? ""; if (empty($userId)) { return "User ID not found"; } if (empty($newPassword)) { return "New password is required"; } $result = _keyhelpmanager_APIRequest( $params, "/users/" . $userId, "PUT", ["password" => $newPassword], ); if ($result["success"]) { _keyhelpmanager_UpdateAccountDetail( $params["serviceid"], "password", $newPassword, ); return "success"; } return $result["error"]; } catch (\Exception $e) { return "Password change failed: " . $e->getMessage(); } } function keyhelpmanager_TestConnection(array $params) { try { $result = _keyhelpmanager_APIRequest($params, "/server/version", "GET"); if (!$result["success"]) { return ["success" => false, "error" => $result["error"]]; } $version = $result["data"]["version"] ?? "Unknown"; return [ "success" => true, "message" => sprintf( "Connection successful! KeyHelp Version: %s", $version, ), ]; } catch (\Exception $e) { return ["success" => false, "error" => $e->getMessage()]; } } // Helper Functions function _keyhelpmanager_GenerateUsername(string $domain): string { $username = preg_replace('/\.[^.]+$/', "", $domain); $username = preg_replace("/[^a-z0-9]/i", "", $username); $username = strtolower(substr($username, 0, 16)); if (strlen($username) < 6) { $username .= rand(1000, 9999); } return $username; } function _keyhelpmanager_GeneratePassword(int $length = 16): string { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+'; $password = ""; $charsLength = strlen($chars); for ($i = 0; $i < $length; $i++) { $password .= $chars[random_int(0, $charsLength - 1)]; } return $password; } function _keyhelpmanager_SaveAccountDetails( int $serviceId, array $details, ): void { try { foreach ($details as $key => $value) { if ($key === "username" || $key === "password") { Capsule::table("tblhosting") ->where("id", $serviceId) ->update([$key => $value]); } } if (isset($details["userid"])) { Capsule::table("tblcustomfieldsvalues")->updateOrInsert( [ "relid" => $serviceId, "fieldid" => _keyhelpmanager_GetCustomFieldId( "KeyHelp User ID", ), ], ["value" => $details["userid"]], ); } } catch (\Exception $e) { logActivity( "KeyHelpManager: Save account details failed - " . $e->getMessage(), ); } } function _keyhelpmanager_GetAccountDetails(int $serviceId): array { try { $service = Capsule::table("tblhosting") ->where("id", $serviceId) ->first(); $userid = Capsule::table("tblcustomfieldsvalues") ->where("relid", $serviceId) ->where( "fieldid", _keyhelpmanager_GetCustomFieldId("KeyHelp User ID"), ) ->value("value"); return [ "username" => $service->username ?? "", "password" => decrypt($service->password ?? ""), "userid" => $userid ?? null, ]; } catch (\Exception $e) { logActivity( "KeyHelpManager: Get account details failed - " . $e->getMessage(), ); return []; } } function _keyhelpmanager_GetUserId(array $params): ?int { $accountDetails = _keyhelpmanager_GetAccountDetails($params["serviceid"]); return $accountDetails["userid"] ?? null; } function _keyhelpmanager_UpdateAccountDetail( int $serviceId, string $field, string $value, ): void { try { if ($field === "username" || $field === "password") { Capsule::table("tblhosting") ->where("id", $serviceId) ->update([$field => $value]); } } catch (\Exception $e) { logActivity( "KeyHelpManager: Update account detail failed - " . $e->getMessage(), ); } } function _keyhelpmanager_DeleteAccountDetails(int $serviceId): void { try { Capsule::table("tblcustomfieldsvalues") ->where("relid", $serviceId) ->delete(); } catch (\Exception $e) { logActivity( "KeyHelpManager: Delete account details failed - " . $e->getMessage(), ); } } function _keyhelpmanager_GetCustomFieldId(string $fieldName): int { try { $field = Capsule::table("tblcustomfields") ->where("fieldname", $fieldName) ->where("type", "product") ->first(); if (!$field) { return Capsule::table("tblcustomfields")->insertGetId([ "type" => "product", "fieldname" => $fieldName, "fieldtype" => "text", "description" => "", "adminonly" => "on", ]); } return $field->id; } catch (\Exception $e) { logActivity( "KeyHelpManager: Get custom field ID failed - " . $e->getMessage(), ); return 0; } } function _keyhelpmanager_FormatBytes(int $bytes): string { $units = ["B", "KB", "MB", "GB", "TB"]; $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0; return number_format($bytes / pow(1024, $power), 2, ".", ",") . " " . $units[$power]; } function _keyhelpmanager_CalculatePercent(int $used, int $limit): float { return $limit > 0 ? round(($used / $limit) * 100, 2) : 0; } /** * Get templates from KeyHelp server */ function _keyhelpmanager_GetTemplates($params = null) { try { // Get server configuration from module settings or server params if ($params && isset($params["serverid"])) { $server = Capsule::table("tblservers") ->where("id", $params["serverid"]) ->first(); if (!$server) { return ["" => "-- No Server Selected --"]; } $apiParams = [ "serverhostname" => $server->hostname, "serveraccesshash" => decrypt($server->accesshash), "serversecure" => $server->secure, ]; // Get additional config options $configOptions = Capsule::table("tblservers") ->where("id", $params["serverid"]) ->first(); if ($configOptions && isset($configOptions->username)) { $apiParams["serverusername"] = $configOptions->username; } } else { return ["" => "-- Configure Product First --"]; } // Fetch templates/plans from KeyHelp $result = _keyhelpmanager_APIRequest($apiParams, "/plans", "GET"); if (!$result["success"]) { logActivity( "KeyHelpManager: Failed to fetch templates - " . $result["error"], ); return ["" => "-- Error loading templates --"]; } $templates = ["" => "-- Select Template --"]; if (isset($result["data"]) && is_array($result["data"])) { foreach ($result["data"] as $template) { $templates[$template["id"]] = $template["name"] ?? "Template " . $template["id"]; } } // Cache templates for 5 minutes $cacheKey = "keyhelpmanager_templates_" . ($params["serverid"] ?? 0); Capsule::table("tblconfiguration")->updateOrInsert( ["setting" => $cacheKey], [ "setting" => $cacheKey, "value" => json_encode($templates), "created_at" => date("Y-m-d H:i:s"), "updated_at" => date("Y-m-d H:i:s"), ], ); return $templates; } catch (\Exception $e) { logActivity( "KeyHelpManager: Get templates failed - " . $e->getMessage(), ); return ["" => "-- Error: " . $e->getMessage() . " --"]; } } /** * Sync templates from KeyHelp server */ function keyhelpmanager_AdminCustomButtonArray() { return [ "Sync Templates" => "SyncTemplates", ]; } function keyhelpmanager_SyncTemplates($params) { try { $templates = _keyhelpmanager_GetTemplates($params); if (count($templates) > 1) { return "success&Templates synchronized successfully"; } else { return "error&Failed to sync templates"; } } catch (\Exception $e) { return "error&" . $e->getMessage(); } }