feat: auto-create custom fields, add try/catch coverage, PHPDoc, and Pint formatting
All checks were successful
Publish Release / release (push) Successful in 10s
All checks were successful
Publish Release / release (push) Successful in 10s
- Auto-create 'Initial Operating System' and 'Initial SSH Key' custom fields via Database::ensureCustomFields() on module load, eliminating the manual modify.sql step - Delete modify.sql (no longer needed) - Add try/catch blocks around every DB operation and API call across all PHP files per CLAUDE.md error handling rules - Add comprehensive PHPDoc to all classes, methods, and properties - Set up Laravel Pint (laravel/pint) with Laravel-style preset for consistent code formatting across the codebase - Add git pre-commit hook (hooks/pre-commit) that runs Pint on staged PHP files, auto-installed via Composer post-install/post-update scripts - Simplify README installation to a single copy-paste command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
<?php
|
||||
|
||||
if (!defined("WHMCS")) {
|
||||
die("This file cannot be accessed directly");
|
||||
if (! defined('WHMCS')) {
|
||||
exit('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
use WHMCS\Module\Server\VirtFusionDirect\ModuleFunctions;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Module;
|
||||
use WHMCS\Database\Capsule;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Database;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Log;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Module;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\ModuleFunctions;
|
||||
|
||||
/**
|
||||
* Returns module metadata consumed by WHMCS.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function VirtFusionDirect_MetaData()
|
||||
{
|
||||
return [
|
||||
@@ -19,50 +26,55 @@ function VirtFusionDirect_MetaData()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product configuration options displayed in the WHMCS product editor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function VirtFusionDirect_ConfigOptions()
|
||||
{
|
||||
return [
|
||||
"defaultHypervisorGroupId" => [
|
||||
"FriendlyName" => "Hypervisor Group ID",
|
||||
"Type" => "text",
|
||||
"Size" => "20",
|
||||
"Description" => "The default hypervisor group ID for server placement.",
|
||||
"Default" => "1",
|
||||
'defaultHypervisorGroupId' => [
|
||||
'FriendlyName' => 'Hypervisor Group ID',
|
||||
'Type' => 'text',
|
||||
'Size' => '20',
|
||||
'Description' => 'The default hypervisor group ID for server placement.',
|
||||
'Default' => '1',
|
||||
],
|
||||
"packageID" => [
|
||||
"FriendlyName" => "Package ID",
|
||||
"Type" => "text",
|
||||
"Size" => "20",
|
||||
"Description" => "The VirtFusion package ID that defines server resources.",
|
||||
"Default" => "1",
|
||||
'packageID' => [
|
||||
'FriendlyName' => 'Package ID',
|
||||
'Type' => 'text',
|
||||
'Size' => '20',
|
||||
'Description' => 'The VirtFusion package ID that defines server resources.',
|
||||
'Default' => '1',
|
||||
],
|
||||
"defaultIPv4" => [
|
||||
"FriendlyName" => "Default IPv4",
|
||||
"Type" => "dropdown",
|
||||
"Options" => "0,1,2,3,4,5,6,7,8,9,10",
|
||||
"Description" => "The default number of IPv4 addresses to assign to each server.",
|
||||
"Default" => "1",
|
||||
'defaultIPv4' => [
|
||||
'FriendlyName' => 'Default IPv4',
|
||||
'Type' => 'dropdown',
|
||||
'Options' => '0,1,2,3,4,5,6,7,8,9,10',
|
||||
'Description' => 'The default number of IPv4 addresses to assign to each server.',
|
||||
'Default' => '1',
|
||||
],
|
||||
"selfServiceMode" => [
|
||||
"FriendlyName" => "Self-Service Mode",
|
||||
"Type" => "dropdown",
|
||||
"Options" => "0|Disabled,1|Hourly,2|Resource Packs,3|Both",
|
||||
"Description" => "Enable VirtFusion self-service billing for users created by this product.",
|
||||
"Default" => "0",
|
||||
'selfServiceMode' => [
|
||||
'FriendlyName' => 'Self-Service Mode',
|
||||
'Type' => 'dropdown',
|
||||
'Options' => '0|Disabled,1|Hourly,2|Resource Packs,3|Both',
|
||||
'Description' => 'Enable VirtFusion self-service billing for users created by this product.',
|
||||
'Default' => '0',
|
||||
],
|
||||
"autoTopOffThreshold" => [
|
||||
"FriendlyName" => "Auto Top-Off Threshold",
|
||||
"Type" => "text",
|
||||
"Size" => "10",
|
||||
"Description" => "Credit balance below which auto top-off triggers during cron. 0 = disabled.",
|
||||
"Default" => "0",
|
||||
'autoTopOffThreshold' => [
|
||||
'FriendlyName' => 'Auto Top-Off Threshold',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Description' => 'Credit balance below which auto top-off triggers during cron. 0 = disabled.',
|
||||
'Default' => '0',
|
||||
],
|
||||
"autoTopOffAmount" => [
|
||||
"FriendlyName" => "Auto Top-Off Amount",
|
||||
"Type" => "text",
|
||||
"Size" => "10",
|
||||
"Description" => "Credit amount to add when auto top-off triggers.",
|
||||
"Default" => "100",
|
||||
'autoTopOffAmount' => [
|
||||
'FriendlyName' => 'Auto Top-Off Amount',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Description' => 'Credit amount to add when auto top-off triggers.',
|
||||
'Default' => '100',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -78,7 +90,7 @@ function VirtFusionDirect_TestConnection(array $params)
|
||||
}
|
||||
|
||||
$url = 'https://' . $hostname . '/api/v1';
|
||||
$module = new Module();
|
||||
$module = new Module;
|
||||
$request = $module->initCurl($password);
|
||||
$data = $request->get($url . '/connect');
|
||||
|
||||
@@ -94,27 +106,33 @@ function VirtFusionDirect_TestConnection(array $params)
|
||||
|
||||
if ($httpCode == 0) {
|
||||
$curlError = $request->getRequestInfo('curl_error');
|
||||
|
||||
return ['success' => false, 'error' => 'Connection failed: ' . ($curlError ?: 'Unable to reach the VirtFusion server. Verify the hostname and that SSL certificates are valid.')];
|
||||
}
|
||||
|
||||
return ['success' => false, 'error' => 'Unexpected response from VirtFusion API (HTTP ' . $httpCode . '). Please check the server configuration.'];
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
return ['success' => false, 'error' => 'Connection test failed: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns custom admin action buttons shown on the service management page.
|
||||
*
|
||||
* @return array Button label => function suffix pairs
|
||||
*/
|
||||
function VirtFusionDirect_AdminCustomButtonArray()
|
||||
{
|
||||
return [
|
||||
"Update Server Object" => "updateServerObject",
|
||||
"Validate Server Config" => "validateServerConfig",
|
||||
'Update Server Object' => 'updateServerObject',
|
||||
'Validate Server Config' => 'validateServerConfig',
|
||||
];
|
||||
}
|
||||
|
||||
function VirtFusionDirect_ServiceSingleSignOn(array $params)
|
||||
{
|
||||
try {
|
||||
$module = new Module();
|
||||
$module = new Module;
|
||||
$token = $module->fetchLoginTokens($params['serviceid']);
|
||||
|
||||
if ($token) {
|
||||
@@ -122,7 +140,7 @@ function VirtFusionDirect_ServiceSingleSignOn(array $params)
|
||||
}
|
||||
|
||||
return ['success' => false, 'errorMsg' => 'Unable to generate a login token. The server may not be active or the VirtFusion API may be unreachable.'];
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'errorMsg' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
@@ -132,64 +150,104 @@ function VirtFusionDirect_ServiceSingleSignOn(array $params)
|
||||
*/
|
||||
function VirtFusionDirect_CreateAccount(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->createAccount($params);
|
||||
return (new ModuleFunctions)->createAccount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspends the VirtFusion server associated with a WHMCS service.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_SuspendAccount(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->suspendAccount($params);
|
||||
return (new ModuleFunctions)->suspendAccount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspends the VirtFusion server associated with a WHMCS service.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_UnsuspendAccount(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->unsuspendAccount($params);
|
||||
return (new ModuleFunctions)->unsuspendAccount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates (deletes) the VirtFusion server associated with a WHMCS service.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_TerminateAccount(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->terminateAccount($params);
|
||||
return (new ModuleFunctions)->terminateAccount($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin custom action: refreshes the local server object from the VirtFusion API.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_updateServerObject(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->updateServerObject($params);
|
||||
return (new ModuleFunctions)->updateServerObject($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows changing of the package of a server
|
||||
*
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
function VirtFusionDirect_ChangePackage(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->changePackage($params);
|
||||
return (new ModuleFunctions)->changePackage($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML fields rendered in the custom admin services tab.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return array Field name => HTML value pairs
|
||||
*/
|
||||
function VirtFusionDirect_AdminServicesTabFields(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->adminServicesTabFields($params);
|
||||
return (new ModuleFunctions)->adminServicesTabFields($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles saving of custom admin services tab field values.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return void
|
||||
*/
|
||||
function VirtFusionDirect_AdminServicesTabFieldsSave(array $params)
|
||||
{
|
||||
(new ModuleFunctions())->adminServicesTabFieldsSave($params);
|
||||
(new ModuleFunctions)->adminServicesTabFieldsSave($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client area template variables and template name for the service overview page.
|
||||
*
|
||||
* @param array $params WHMCS module parameters
|
||||
* @return array Smarty template variables and 'templatefile' key
|
||||
*/
|
||||
function VirtFusionDirect_ClientArea(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->clientArea($params);
|
||||
return (new ModuleFunctions)->clientArea($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates server configuration via dry run without creating the server.
|
||||
*
|
||||
* @param array $params
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_validateServerConfig(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->validateServerConfig($params);
|
||||
return (new ModuleFunctions)->validateServerConfig($params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,20 +256,20 @@ function VirtFusionDirect_validateServerConfig(array $params)
|
||||
* Updates tblhosting with disk and bandwidth usage data from VirtFusion.
|
||||
* Fields updated: diskused, disklimit, bwused, bwlimit, lastupdate
|
||||
*
|
||||
* @param array $params Server access credentials
|
||||
* @param array $params Server access credentials
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_UsageUpdate(array $params)
|
||||
{
|
||||
try {
|
||||
$module = new Module();
|
||||
$module = new Module;
|
||||
$cp = $module->getCP($params['serverid']);
|
||||
|
||||
if (!$cp) {
|
||||
if (! $cp) {
|
||||
return 'No control server found for usage update.';
|
||||
}
|
||||
|
||||
$services = \WHMCS\Database\Capsule::table('tblhosting')
|
||||
$services = Capsule::table('tblhosting')
|
||||
->where('server', $params['serverid'])
|
||||
->where('domainstatus', 'Active')
|
||||
->get();
|
||||
@@ -219,7 +277,7 @@ function VirtFusionDirect_UsageUpdate(array $params)
|
||||
foreach ($services as $service) {
|
||||
try {
|
||||
$systemService = Database::getSystemService($service->id);
|
||||
if (!$systemService) {
|
||||
if (! $systemService) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -231,7 +289,7 @@ function VirtFusionDirect_UsageUpdate(array $params)
|
||||
}
|
||||
|
||||
$serverData = json_decode($data, true);
|
||||
if (!isset($serverData['data'])) {
|
||||
if (! isset($serverData['data'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -255,15 +313,15 @@ function VirtFusionDirect_UsageUpdate(array $params)
|
||||
$update['bwlimit'] = $trafficGB > 0 ? $trafficGB * 1024 : 0;
|
||||
}
|
||||
|
||||
if (!empty($update)) {
|
||||
if (! empty($update)) {
|
||||
$update['lastupdate'] = date('Y-m-d H:i:s');
|
||||
\WHMCS\Database\Capsule::table('tblhosting')
|
||||
Capsule::table('tblhosting')
|
||||
->where('id', $service->id)
|
||||
->update($update);
|
||||
}
|
||||
|
||||
// Self-service auto top-off
|
||||
$product = \WHMCS\Database\Capsule::table('tblproducts')
|
||||
$product = Capsule::table('tblproducts')
|
||||
->where('id', $service->packageid)
|
||||
->first();
|
||||
|
||||
@@ -278,24 +336,24 @@ function VirtFusionDirect_UsageUpdate(array $params)
|
||||
$credit = $usageInner['credit'] ?? $usageInner['balance'] ?? null;
|
||||
if ($credit !== null && (float) $credit < $threshold) {
|
||||
$module->addSelfServiceCredit($service->id, $topOffAmount, 'Auto top-off');
|
||||
\WHMCS\Module\Server\VirtFusionDirect\Log::insert(
|
||||
Log::insert(
|
||||
'UsageUpdate:autoTopOff',
|
||||
['serviceId' => $service->id, 'credit' => $credit, 'threshold' => $threshold],
|
||||
['amount' => $topOffAmount]
|
||||
['amount' => $topOffAmount],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// Log but continue processing other services
|
||||
\WHMCS\Module\Server\VirtFusionDirect\Log::insert('UsageUpdate:service:' . $service->id, [], $e->getMessage());
|
||||
Log::insert('UsageUpdate:service:' . $service->id, [], $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 'success';
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return 'Usage update failed: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,82 +2,97 @@
|
||||
|
||||
require dirname(__DIR__, 3) . '/init.php';
|
||||
|
||||
/**
|
||||
* Admin-facing AJAX API endpoint.
|
||||
*
|
||||
* Requires WHMCS admin authentication. Provides server data lookup
|
||||
* and user impersonation for the admin services tab.
|
||||
*/
|
||||
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Database;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Log;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Module;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\ServerResource;
|
||||
|
||||
$vf = new Module();
|
||||
$vf = new Module;
|
||||
|
||||
$vf->adminOnly();
|
||||
try {
|
||||
|
||||
switch ($vf->validateAction(true)) {
|
||||
$vf->adminOnly();
|
||||
|
||||
/**
|
||||
* Get server information.
|
||||
*/
|
||||
case 'serverData':
|
||||
switch ($vf->validateAction(true)) {
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
/**
|
||||
* Get server information.
|
||||
*/
|
||||
case 'serverData':
|
||||
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found.'], true, true, 404);
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
|
||||
if (! $whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found.'], true, true, 404);
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_array($whmcsService->domainstatus, ['Pending', 'Terminated', 'Cancelled', 'Fraud'], true)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Server is not Active, Suspended or Completed. Not fetching remote data.'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
|
||||
if (! $data) {
|
||||
$vf->output(['success' => false, 'errors' => 'No data returned from VirtFusion.'], true, true, 502);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource)->process($data)], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_array($whmcsService->domainstatus, ['Pending', 'Terminated', 'Cancelled', 'Fraud'], true)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Server is not Active, Suspended or Completed. Not fetching remote data.'], true, true, 400);
|
||||
/**
|
||||
* Impersonate server owner.
|
||||
*/
|
||||
case 'impersonateServerOwner':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
if (! $service) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found'], true, true, 404);
|
||||
break;
|
||||
}
|
||||
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
if (! $whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'WHMCS service not found'], true, true, 404);
|
||||
break;
|
||||
}
|
||||
|
||||
$cp = $vf->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
$vf->output(['success' => false, 'errors' => 'Control server not found'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$request = $vf->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/users/' . (int) $whmcsService->userid . '/byExtRelation');
|
||||
|
||||
if ($request->getRequestInfo('http_code') === 200) {
|
||||
$vf->output(['success' => true, 'url' => $cp['base_url'], 'user' => json_decode($data, true)['data']], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch user data'], true, true, 502);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
default:
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$vf->output(['success' => false, 'errors' => 'No data returned from VirtFusion.'], true, true, 502);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Impersonate server owner.
|
||||
*/
|
||||
case 'impersonateServerOwner':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
if (!$service) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found'], true, true, 404);
|
||||
break;
|
||||
}
|
||||
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
if (!$whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'WHMCS service not found'], true, true, 404);
|
||||
break;
|
||||
}
|
||||
|
||||
$cp = $vf->getCP($whmcsService->server);
|
||||
if (!$cp) {
|
||||
$vf->output(['success' => false, 'errors' => 'Control server not found'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$request = $vf->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/users/' . (int) $whmcsService->userid . '/byExtRelation');
|
||||
|
||||
if ($request->getRequestInfo('http_code') === 200) {
|
||||
$vf->output(['success' => true, 'url' => $cp['base_url'], 'user' => json_decode($data, true)['data']], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch user data'], true, true, 502);
|
||||
break;
|
||||
|
||||
default:
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
} catch (Exception $e) {
|
||||
Log::insert('admin.php', [], $e->getMessage());
|
||||
$vf->output(['success' => false, 'errors' => 'An unexpected error occurred'], true, true, 500);
|
||||
}
|
||||
|
||||
@@ -2,399 +2,414 @@
|
||||
|
||||
require dirname(__DIR__, 3) . '/init.php';
|
||||
|
||||
/**
|
||||
* Client-facing AJAX API endpoint.
|
||||
*
|
||||
* Authenticated by WHMCS session + service ownership validation.
|
||||
* POST for mutations (power, rebuild, rename, credit), GET for reads (serverData, templates, backups).
|
||||
*/
|
||||
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Log;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Module;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\ServerResource;
|
||||
|
||||
$vf = new Module();
|
||||
$vf = new Module;
|
||||
|
||||
$vf->isAuthenticated();
|
||||
try {
|
||||
|
||||
$action = $vf->validateAction(true);
|
||||
$vf->isAuthenticated();
|
||||
|
||||
switch ($action) {
|
||||
$action = $vf->validateAction(true);
|
||||
|
||||
/**
|
||||
* Reset Password.
|
||||
*/
|
||||
case 'resetPassword':
|
||||
switch ($action) {
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
$client = $vf->validateUserOwnsService($serviceID);
|
||||
/**
|
||||
* Reset Password.
|
||||
*/
|
||||
case 'resetPassword':
|
||||
|
||||
if (!$client) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
$client = $vf->validateUserOwnsService($serviceID);
|
||||
|
||||
if (! $client) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->resetUserPassword($serviceID, $client);
|
||||
|
||||
if ($data) {
|
||||
$vf->output(['success' => true, 'data' => $data->data], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->resetUserPassword($serviceID, $client);
|
||||
/**
|
||||
* Get server information.
|
||||
*/
|
||||
case 'serverData':
|
||||
|
||||
if ($data) {
|
||||
$vf->output(['success' => true, 'data' => $data->data], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
|
||||
if ($data) {
|
||||
$vf->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource)->process($data)], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve server data'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
|
||||
break;
|
||||
/**
|
||||
* Login as server owner.
|
||||
*/
|
||||
case 'loginAsServerOwner':
|
||||
|
||||
/**
|
||||
* Get server information.
|
||||
*/
|
||||
case 'serverData':
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
$token = $vf->fetchLoginTokens($serviceID);
|
||||
|
||||
if ($token) {
|
||||
$vf->output(['success' => true, 'token_url' => $token], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to generate login token'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
/**
|
||||
* Power management actions: boot, shutdown, restart, poweroff
|
||||
*/
|
||||
case 'powerAction':
|
||||
|
||||
if ($data) {
|
||||
$vf->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$powerAction = isset($_POST['powerAction']) ? preg_replace('/[^a-zA-Z]/', '', $_POST['powerAction']) : '';
|
||||
$allowedActions = ['boot', 'shutdown', 'restart', 'poweroff'];
|
||||
|
||||
if (! in_array($powerAction, $allowedActions, true)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid power action'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->serverPowerAction($serviceID, $powerAction);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['action' => $powerAction, 'message' => 'Power action queued successfully']], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Power action failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve server data'], true, true, 500);
|
||||
break;
|
||||
/**
|
||||
* Rebuild/reinstall server with new OS.
|
||||
*/
|
||||
case 'rebuild':
|
||||
|
||||
/**
|
||||
* Login as server owner.
|
||||
*/
|
||||
case 'loginAsServerOwner':
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
$osId = isset($_POST['osId']) ? (int) $_POST['osId'] : 0;
|
||||
$hostname = isset($_POST['hostname']) ? preg_replace('/[^a-zA-Z0-9.\-]/', '', $_POST['hostname']) : null;
|
||||
|
||||
if ($osId <= 0) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid operating system ID'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->rebuildServer($serviceID, $osId, $hostname);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server rebuild initiated successfully']], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rebuild failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$token = $vf->fetchLoginTokens($serviceID);
|
||||
/**
|
||||
* Rename server.
|
||||
*/
|
||||
case 'rename':
|
||||
|
||||
if ($token) {
|
||||
$vf->output(['success' => true, 'token_url' => $token], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$newName = isset($_POST['name']) ? trim($_POST['name']) : '';
|
||||
|
||||
if (empty($newName) || strlen($newName) > 63 || ! preg_match('/^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$/', $newName)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid server name'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->renameServer($serviceID, $newName);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server renamed successfully']], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rename failed'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to generate login token'], true, true, 500);
|
||||
break;
|
||||
/**
|
||||
* Get available OS templates for rebuild.
|
||||
*/
|
||||
case 'osTemplates':
|
||||
|
||||
/**
|
||||
* Power management actions: boot, shutdown, restart, poweroff
|
||||
*/
|
||||
case 'powerAction':
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
$templates = $vf->fetchOsTemplates($serviceID);
|
||||
|
||||
if ($templates !== false) {
|
||||
$vf->output(['success' => true, 'data' => $templates], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch OS templates'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$powerAction = isset($_POST['powerAction']) ? preg_replace('/[^a-zA-Z]/', '', $_POST['powerAction']) : '';
|
||||
$allowedActions = ['boot', 'shutdown', 'restart', 'poweroff'];
|
||||
// =================================================================
|
||||
// Server Password Reset
|
||||
// =================================================================
|
||||
|
||||
if (!in_array($powerAction, $allowedActions, true)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid power action'], true, true, 400);
|
||||
/**
|
||||
* Reset server root password.
|
||||
*/
|
||||
case 'resetServerPassword':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->resetServerPassword($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->serverPowerAction($serviceID, $powerAction);
|
||||
// =================================================================
|
||||
// Backup Listing
|
||||
// =================================================================
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['action' => $powerAction, 'message' => 'Power action queued successfully']], true, true, 200);
|
||||
/**
|
||||
* Get server backups.
|
||||
*/
|
||||
case 'backups':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getServerBackups($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve backups'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Power action failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
// =================================================================
|
||||
// Traffic Statistics
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Rebuild/reinstall server with new OS.
|
||||
*/
|
||||
case 'rebuild':
|
||||
/**
|
||||
* Get traffic statistics for a server.
|
||||
*/
|
||||
case 'trafficStats':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getTrafficStats($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve traffic statistics'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$osId = isset($_POST['osId']) ? (int) $_POST['osId'] : 0;
|
||||
$hostname = isset($_POST['hostname']) ? preg_replace('/[^a-zA-Z0-9.\-]/', '', $_POST['hostname']) : null;
|
||||
// =================================================================
|
||||
// VNC Console
|
||||
// =================================================================
|
||||
|
||||
if ($osId <= 0) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid operating system ID'], true, true, 400);
|
||||
/**
|
||||
* Get VNC console URL.
|
||||
*/
|
||||
case 'vnc':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getVncConsole($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'VNC console unavailable. The server may be powered off or VNC is not supported.'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->rebuildServer($serviceID, $osId, $hostname);
|
||||
/**
|
||||
* Toggle VNC on/off.
|
||||
*/
|
||||
case 'toggleVnc':
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server rebuild initiated successfully']], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$enabled = isset($_POST['enabled']) && $_POST['enabled'] === '1';
|
||||
$result = $vf->toggleVnc($serviceID, $enabled);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to toggle VNC'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rebuild failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
// =================================================================
|
||||
// Self Service — Credit & Usage
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Rename server.
|
||||
*/
|
||||
case 'rename':
|
||||
/**
|
||||
* Get self-service usage data.
|
||||
*/
|
||||
case 'selfServiceUsage':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getSelfServiceUsage($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve self-service usage data'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$newName = isset($_POST['name']) ? trim($_POST['name']) : '';
|
||||
/**
|
||||
* Get self-service billing report.
|
||||
*/
|
||||
case 'selfServiceReport':
|
||||
|
||||
if (empty($newName) || strlen($newName) > 63 || !preg_match('/^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$/', $newName)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid server name'], true, true, 400);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getSelfServiceReport($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve self-service report'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->renameServer($serviceID, $newName);
|
||||
/**
|
||||
* Add self-service credit.
|
||||
*/
|
||||
case 'selfServiceAddCredit':
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server renamed successfully']], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (! $vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$tokens = isset($_POST['tokens']) ? (float) $_POST['tokens'] : 0;
|
||||
if ($tokens <= 0) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid credit amount. Must be a positive number.'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->addSelfServiceCredit($serviceID, $tokens);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to add credit'], true, true, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rename failed'], true, true, 500);
|
||||
break;
|
||||
default:
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available OS templates for rebuild.
|
||||
*/
|
||||
case 'osTemplates':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$templates = $vf->fetchOsTemplates($serviceID);
|
||||
|
||||
if ($templates !== false) {
|
||||
$vf->output(['success' => true, 'data' => $templates], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch OS templates'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// Server Password Reset
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Reset server root password.
|
||||
*/
|
||||
case 'resetServerPassword':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->resetServerPassword($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// Backup Listing
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get server backups.
|
||||
*/
|
||||
case 'backups':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getServerBackups($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve backups'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// Traffic Statistics
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get traffic statistics for a server.
|
||||
*/
|
||||
case 'trafficStats':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getTrafficStats($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve traffic statistics'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// VNC Console
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get VNC console URL.
|
||||
*/
|
||||
case 'vnc':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getVncConsole($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'VNC console unavailable. The server may be powered off or VNC is not supported.'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Toggle VNC on/off.
|
||||
*/
|
||||
case 'toggleVnc':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$enabled = isset($_POST['enabled']) && $_POST['enabled'] === '1';
|
||||
$result = $vf->toggleVnc($serviceID, $enabled);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to toggle VNC'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// Self Service — Credit & Usage
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get self-service usage data.
|
||||
*/
|
||||
case 'selfServiceUsage':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getSelfServiceUsage($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve self-service usage data'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Get self-service billing report.
|
||||
*/
|
||||
case 'selfServiceReport':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->getSelfServiceReport($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve self-service report'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Add self-service credit.
|
||||
*/
|
||||
case 'selfServiceAddCredit':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
break;
|
||||
}
|
||||
|
||||
$tokens = isset($_POST['tokens']) ? (float) $_POST['tokens'] : 0;
|
||||
if ($tokens <= 0) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid credit amount. Must be a positive number.'], true, true, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $vf->addSelfServiceCredit($serviceID, $tokens);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
break;
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to add credit'], true, true, 500);
|
||||
break;
|
||||
|
||||
default:
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
} catch (Exception $e) {
|
||||
Log::insert('client.php', [], $e->getMessage());
|
||||
$vf->output(['success' => false, 'errors' => 'An unexpected error occurred'], true, true, 500);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
if (!defined("WHMCS")) {
|
||||
die("This file cannot be accessed directly");
|
||||
if (! defined('WHMCS')) {
|
||||
exit('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -16,4 +16,4 @@ return [
|
||||
'cpuCores' => 'CPU Cores',
|
||||
'networkProfile' => 'Network Type',
|
||||
'storageProfile' => 'Storage Type',
|
||||
];
|
||||
];
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
use WHMCS\Database\Capsule;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\ConfigureService;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Database;
|
||||
use WHMCS\Module\Server\VirtFusionDirect\Module;
|
||||
|
||||
if (!defined("WHMCS")) {
|
||||
die("This file cannot be accessed directly");
|
||||
if (! defined('WHMCS')) {
|
||||
exit('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,47 +18,51 @@ if (!defined("WHMCS")) {
|
||||
add_hook('ShoppingCartValidateCheckout', 1, function ($vars) {
|
||||
$errors = [];
|
||||
|
||||
if (!isset($_SESSION['cart']['products']) || !is_array($_SESSION['cart']['products'])) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
foreach ($_SESSION['cart']['products'] as $key => $product) {
|
||||
$pid = $product['pid'] ?? null;
|
||||
if (!$pid) {
|
||||
continue;
|
||||
try {
|
||||
if (! isset($_SESSION['cart']['products']) || ! is_array($_SESSION['cart']['products'])) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$dbProduct = \WHMCS\Database\Capsule::table('tblproducts')
|
||||
->where('id', $pid)
|
||||
->where('servertype', 'VirtFusionDirect')
|
||||
->first();
|
||||
foreach ($_SESSION['cart']['products'] as $key => $product) {
|
||||
$pid = $product['pid'] ?? null;
|
||||
if (! $pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$dbProduct) {
|
||||
continue;
|
||||
}
|
||||
$dbProduct = Capsule::table('tblproducts')
|
||||
->where('id', $pid)
|
||||
->where('servertype', 'VirtFusionDirect')
|
||||
->first();
|
||||
|
||||
// Check if Initial Operating System custom field has a value
|
||||
if (isset($product['customfields']) && is_array($product['customfields'])) {
|
||||
$osSelected = false;
|
||||
$customFields = \WHMCS\Database\Capsule::table('tblcustomfields')
|
||||
->where('relid', $pid)
|
||||
->where('type', 'product')
|
||||
->get();
|
||||
if (! $dbProduct) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($customFields as $field) {
|
||||
if (strtolower(str_replace(' ', '', $field->fieldname)) === 'initialoperatingsystem') {
|
||||
$fieldValue = $product['customfields'][$field->id] ?? '';
|
||||
if (!empty($fieldValue) && is_numeric($fieldValue)) {
|
||||
$osSelected = true;
|
||||
// Check if Initial Operating System custom field has a value
|
||||
if (isset($product['customfields']) && is_array($product['customfields'])) {
|
||||
$osSelected = false;
|
||||
$customFields = Capsule::table('tblcustomfields')
|
||||
->where('relid', $pid)
|
||||
->where('type', 'product')
|
||||
->get();
|
||||
|
||||
foreach ($customFields as $field) {
|
||||
if (strtolower(str_replace(' ', '', $field->fieldname)) === 'initialoperatingsystem') {
|
||||
$fieldValue = $product['customfields'][$field->id] ?? '';
|
||||
if (! empty($fieldValue) && is_numeric($fieldValue)) {
|
||||
$osSelected = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $osSelected) {
|
||||
$errors[] = 'Please select an Operating System for your VPS order.';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$osSelected) {
|
||||
$errors[] = 'Please select an Operating System for your VPS order.';
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Don't block checkout on internal errors
|
||||
}
|
||||
|
||||
return $errors;
|
||||
@@ -70,22 +76,22 @@ add_hook('ShoppingCartValidateCheckout', 1, function ($vars) {
|
||||
* Works with all WHMCS themes by using vanilla JavaScript and standard form-control classes.
|
||||
*/
|
||||
add_hook('ClientAreaFooterOutput', 1, function ($vars) {
|
||||
if (!isset($vars['productinfo']['module']) || $vars['productinfo']['module'] !== 'VirtFusionDirect') {
|
||||
if (! isset($vars['productinfo']['module']) || $vars['productinfo']['module'] !== 'VirtFusionDirect') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$cs = new ConfigureService();
|
||||
$cs = new ConfigureService;
|
||||
|
||||
$templates_data = $cs->fetchTemplates(
|
||||
$cs->fetchPackageByDbId($vars['productinfo']['pid']) ?? $cs->fetchPackageId($vars['productinfo']['name'])
|
||||
$cs->fetchPackageByDbId($vars['productinfo']['pid']) ?? $cs->fetchPackageId($vars['productinfo']['name']),
|
||||
);
|
||||
|
||||
if (empty($templates_data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$vfServer = \WHMCS\Database\Capsule::table('tblservers')
|
||||
$vfServer = Capsule::table('tblservers')
|
||||
->where('type', 'VirtFusionDirect')
|
||||
->where('disabled', 0)
|
||||
->first();
|
||||
@@ -93,7 +99,7 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) {
|
||||
|
||||
$galleryData = [
|
||||
'baseUrl' => $baseUrl,
|
||||
'categories' => \WHMCS\Module\Server\VirtFusionDirect\Module::groupOsTemplates($templates_data['data'] ?? [], true),
|
||||
'categories' => Module::groupOsTemplates($templates_data['data'] ?? [], true),
|
||||
];
|
||||
|
||||
$sshKeys = [];
|
||||
@@ -105,9 +111,10 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) {
|
||||
if ($sshKey['enabled'] === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $sshKey['id'],
|
||||
'name' => htmlspecialchars($sshKey['name'], ENT_QUOTES, 'UTF-8')
|
||||
'name' => htmlspecialchars($sshKey['name'], ENT_QUOTES, 'UTF-8'),
|
||||
];
|
||||
}, $sshKeysData['data'])));
|
||||
}
|
||||
@@ -134,17 +141,17 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) {
|
||||
|
||||
$systemUrl = Database::getSystemUrl();
|
||||
|
||||
return "
|
||||
<link href=\"" . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . "modules/servers/VirtFusionDirect/templates/css/module.css?v=" . time() . "\" rel=\"stylesheet\">
|
||||
<script src=\"" . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . "modules/servers/VirtFusionDirect/templates/js/keygen.js?v=" . time() . "\"></script>
|
||||
return '
|
||||
<link href="' . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . 'modules/servers/VirtFusionDirect/templates/css/module.css?v=' . time() . '" rel="stylesheet">
|
||||
<script src="' . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . 'modules/servers/VirtFusionDirect/templates/js/keygen.js?v=' . time() . "\"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var osGalleryData = " . json_encode($galleryData, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";
|
||||
var sshKeys = " . json_encode($sshKeysOptions, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";
|
||||
var osGalleryData = " . json_encode($galleryData, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ';
|
||||
var sshKeys = ' . json_encode($sshKeysOptions, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";
|
||||
|
||||
var osInputField = document.querySelector('[name=\"customfield[" . (int) $osFieldId . "]\"]');
|
||||
var sshInputField = " . ($sshFieldId !== null ? "document.querySelector('[name=\"customfield[" . (int) $sshFieldId . "]\"]')" : "null") . ";
|
||||
var sshInputLabel = " . ($sshFieldId !== null ? "document.querySelector('[for=\"customfield" . (int) $sshFieldId . "\"]')" : "null") . ";
|
||||
var sshInputField = " . ($sshFieldId !== null ? "document.querySelector('[name=\"customfield[" . (int) $sshFieldId . "]\"]')" : 'null') . ';
|
||||
var sshInputLabel = ' . ($sshFieldId !== null ? "document.querySelector('[for=\"customfield" . (int) $sshFieldId . "\"]')" : 'null') . ";
|
||||
|
||||
if (!osInputField) return;
|
||||
|
||||
@@ -564,7 +571,7 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) {
|
||||
});
|
||||
</script>
|
||||
";
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
// Silently fail - don't break the checkout page
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,41 +2,73 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Static methods that generate HTML fragments for the WHMCS admin services tab.
|
||||
*/
|
||||
class AdminHTML
|
||||
{
|
||||
|
||||
/**
|
||||
* Render the "Impersonate Server Owner" button for the admin services tab.
|
||||
*
|
||||
* @param string $systemUrl WHMCS system URL
|
||||
* @param int $serviceId VirtFusion server ID
|
||||
* @return string HTML button markup
|
||||
*/
|
||||
public static function options($systemUrl, $serviceId)
|
||||
{
|
||||
$systemUrl = htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return <<<EOT
|
||||
<button onclick="impersonateServerOwner('${serviceId}', '${systemUrl}')" type="button" class="btn btn-primary">Impersonate Server Owner</button>
|
||||
<span class="text-info"> A valid VirtFusion admin session in the same browser is required for this functionality to work.</span>
|
||||
EOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a read-only textarea containing the raw VirtFusion server JSON object.
|
||||
*
|
||||
* @param string $serverObject JSON-encoded server object from the VirtFusion API
|
||||
* @return string HTML textarea markup
|
||||
*/
|
||||
public static function serverObject($serverObject)
|
||||
{
|
||||
$serverObject = htmlspecialchars($serverObject, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return <<<EOT
|
||||
<textarea class="form-control" name="modulefields[1]" rows="10" style="width: 100%" disabled>${serverObject}</textarea>
|
||||
EOT;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an editable text input for the VirtFusion server ID field.
|
||||
*
|
||||
* @param int $serverId Current VirtFusion server ID
|
||||
* @return string HTML input markup with a warning note
|
||||
*/
|
||||
public static function serverId($serverId)
|
||||
{
|
||||
$serverId = (int) $serverId;
|
||||
|
||||
return <<<EOT
|
||||
<input type="text" class="form-control input-200 input-inline" name="modulefields[0]" size="20" value="${serverId}" />
|
||||
<span class="text-info"> Changing the Sever ID manually is not recommended. Alterations to this field are usually handled automatically.</span>
|
||||
EOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the inline server info panel for the admin services tab, including CSS/JS assets.
|
||||
*
|
||||
* @param string $systemUrl WHMCS system URL (used to build asset and AJAX URLs)
|
||||
* @param int $serviceId VirtFusion server ID passed to the JS data-loader
|
||||
* @return string HTML panel markup with embedded script and asset tags
|
||||
*/
|
||||
public static function serverInfo($systemUrl, $serviceId)
|
||||
{
|
||||
$systemUrl = htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8');
|
||||
$serviceId = (int) $serviceId;
|
||||
$cacheV = time();
|
||||
|
||||
return <<<EOT
|
||||
<link href="${systemUrl}modules/servers/VirtFusionDirect/templates/css/module.css?v=${cacheV}" rel="stylesheet">
|
||||
<script src="${systemUrl}modules/servers/VirtFusionDirect/templates/js/module.js?v=${cacheV}"></script>
|
||||
@@ -117,4 +149,4 @@ EOT;
|
||||
<script>vfServerDataAdmin("${serviceId}","${systemUrl}");</script>
|
||||
EOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Two-tier cache: uses Redis when the ext-redis extension is available, with an atomic
|
||||
* filesystem fallback stored in the system temp directory.
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
const PREFIX = 'vfd:';
|
||||
@@ -28,19 +32,22 @@ class Cache
|
||||
return self::$redis;
|
||||
}
|
||||
|
||||
if (!extension_loaded('redis')) {
|
||||
if (! extension_loaded('redis')) {
|
||||
self::$redisAvailable = false;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = new \Redis();
|
||||
$redis = new \Redis;
|
||||
$redis->connect('127.0.0.1', 6379, 1.0);
|
||||
self::$redis = $redis;
|
||||
self::$redisAvailable = true;
|
||||
|
||||
return $redis;
|
||||
} catch (\Exception $e) {
|
||||
self::$redisAvailable = false;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -55,11 +62,12 @@ class Cache
|
||||
}
|
||||
|
||||
$dir = sys_get_temp_dir() . '/vfd_cache';
|
||||
if (!is_dir($dir)) {
|
||||
if (! is_dir($dir)) {
|
||||
@mkdir($dir, 0700, true);
|
||||
}
|
||||
|
||||
self::$fileDir = $dir;
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
@@ -74,7 +82,7 @@ class Cache
|
||||
/**
|
||||
* Get a cached value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $key
|
||||
* @return mixed|null Returns null on miss
|
||||
*/
|
||||
public static function get($key)
|
||||
@@ -87,6 +95,7 @@ class Cache
|
||||
if ($data !== false) {
|
||||
return json_decode($data, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
// Fall through to file cache
|
||||
@@ -95,7 +104,7 @@ class Cache
|
||||
|
||||
// File cache fallback
|
||||
$path = self::filePath($key);
|
||||
if (!file_exists($path)) {
|
||||
if (! file_exists($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -105,13 +114,15 @@ class Cache
|
||||
}
|
||||
|
||||
$entry = json_decode($raw, true);
|
||||
if (!$entry || !isset($entry['expires']) || !isset($entry['data'])) {
|
||||
if (! $entry || ! isset($entry['expires']) || ! isset($entry['data'])) {
|
||||
@unlink($path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($entry['expires'] < time()) {
|
||||
@unlink($path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -121,9 +132,9 @@ class Cache
|
||||
/**
|
||||
* Store a value in cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time-to-live in seconds
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time-to-live in seconds
|
||||
*/
|
||||
public static function set($key, $value, $ttl = 300)
|
||||
{
|
||||
@@ -132,6 +143,7 @@ class Cache
|
||||
if ($redis) {
|
||||
try {
|
||||
$redis->setex(self::PREFIX . $key, $ttl, json_encode($value));
|
||||
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
// Fall through to file cache
|
||||
@@ -151,7 +163,7 @@ class Cache
|
||||
/**
|
||||
* Delete a cached value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $key
|
||||
*/
|
||||
public static function forget($key)
|
||||
{
|
||||
@@ -169,5 +181,4 @@ class Cache
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,13 +5,30 @@ namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
use WHMCS\Database\Capsule as DB;
|
||||
use WHMCS\User\User;
|
||||
|
||||
/**
|
||||
* Handles order-time and provisioning-time operations for VirtFusion servers.
|
||||
*
|
||||
* Extends Module to provide package discovery, OS template fetching, server build
|
||||
* initialization, and SSH key retrieval/creation. Used during WHMCS checkout and
|
||||
* account creation flows rather than ongoing service management.
|
||||
*/
|
||||
class ConfigureService extends Module
|
||||
{
|
||||
/**
|
||||
* @var array|false $cp
|
||||
* The first available VirtFusion control panel connection, as returned by
|
||||
* getCP(). Holds server URL and API token used for all API calls in this
|
||||
* class. False if no active VirtFusion server is configured in WHMCS.
|
||||
*
|
||||
* @var array|false
|
||||
*/
|
||||
private array|bool $cp;
|
||||
|
||||
/**
|
||||
* Initialize the service configurator with the first available VirtFusion server.
|
||||
*
|
||||
* Calls the parent Module constructor then resolves the control panel connection
|
||||
* so all methods in this class have a ready API endpoint.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@@ -19,208 +36,298 @@ class ConfigureService extends Module
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return int|null
|
||||
* @throws JsonException
|
||||
* Find a VirtFusion package ID by its name via the API.
|
||||
*
|
||||
* Searches the packages list for an enabled package whose name matches
|
||||
* exactly. Result is cached for 10 minutes. Returns null if not found
|
||||
* or if no control panel is available.
|
||||
*
|
||||
* @param string $packageName Exact package name as configured in VirtFusion.
|
||||
* @return int|null Package ID, or null if not found.
|
||||
*/
|
||||
public function fetchPackageId(string $packageName): ?int
|
||||
{
|
||||
$cacheKey = 'pkg_name:' . md5($packageName);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if (!$this->cp) return null;
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$response = $request->get(
|
||||
sprintf("%s/packages", $this->cp['url'])
|
||||
);
|
||||
|
||||
$packages = $this->decodeResponseFromJson($response);
|
||||
|
||||
foreach ($packages['data'] as $package) {
|
||||
if ($package['name'] === $packageName && $package['enabled'] === true) {
|
||||
Cache::set($cacheKey, $package['id'], 600);
|
||||
return $package['id'];
|
||||
try {
|
||||
$cacheKey = 'pkg_name:' . md5($packageName);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if (! $this->cp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$response = $request->get(
|
||||
sprintf('%s/packages', $this->cp['url']),
|
||||
);
|
||||
|
||||
$packages = $this->decodeResponseFromJson($response);
|
||||
|
||||
foreach ($packages['data'] as $package) {
|
||||
if ($package['name'] === $packageName && $package['enabled'] === true) {
|
||||
Cache::set($cacheKey, $package['id'], 600);
|
||||
|
||||
return $package['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $productId
|
||||
* @return int|null
|
||||
* Get the VirtFusion package ID from a WHMCS product's config option.
|
||||
*
|
||||
* Reads configoption2 directly from the tblproducts database record for
|
||||
* the given WHMCS product ID. Returns null if the product does not exist.
|
||||
*
|
||||
* @param int $productId WHMCS product (tblproducts) ID.
|
||||
* @return int|null VirtFusion package ID, or null if the product is not found.
|
||||
*/
|
||||
public function fetchPackageByDbId(int $productId): ?int
|
||||
{
|
||||
$product = DB::table('tblproducts')->where('id', $productId)->first();
|
||||
try {
|
||||
$product = DB::table('tblproducts')->where('id', $productId)->first();
|
||||
|
||||
if (is_null($product)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $product->configoption2;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
if (is_null($product)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$product->configoption2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $serverPackageId
|
||||
* @return array|null
|
||||
* @throws JsonException
|
||||
* Fetch the available OS templates for a given VirtFusion server package.
|
||||
*
|
||||
* Queries the VirtFusion API for templates compatible with the specified
|
||||
* package spec ID. Result is cached for 10 minutes. Returns null if no
|
||||
* package ID is provided or no control panel is available.
|
||||
*
|
||||
* @param int|null $serverPackageId VirtFusion server package spec ID.
|
||||
* @return array|null Template list from the API, or null on failure.
|
||||
*/
|
||||
public function fetchTemplates(?int $serverPackageId): ?array
|
||||
{
|
||||
if (is_null($serverPackageId)) {
|
||||
try {
|
||||
if (is_null($serverPackageId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheKey = 'tpl:' . $serverPackageId;
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if (! $this->cp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$response = $request->get(
|
||||
sprintf('%s/media/templates/fromServerPackageSpec/%d', $this->cp['url'], $serverPackageId),
|
||||
);
|
||||
|
||||
$result = $this->decodeResponseFromJson($response);
|
||||
Cache::set($cacheKey, $result, 600);
|
||||
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheKey = 'tpl:' . $serverPackageId;
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if (!$this->cp) return null;
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$response = $request->get(
|
||||
sprintf("%s/media/templates/fromServerPackageSpec/%d", $this->cp['url'], $serverPackageId)
|
||||
);
|
||||
|
||||
$result = $this->decodeResponseFromJson($response);
|
||||
Cache::set($cacheKey, $result, 600);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|null $user
|
||||
* @return array|null
|
||||
* @throws JsonException
|
||||
* Get the SSH keys registered for a VirtFusion user.
|
||||
*
|
||||
* Looks up the VirtFusion account for the given WHMCS user via external
|
||||
* relation ID, then fetches their SSH key list from the API. Returns null
|
||||
* if the user is not found in VirtFusion or no control panel is available.
|
||||
*
|
||||
* @param User|null $user WHMCS User object.
|
||||
* @return array|null SSH key list from the API, or null on failure.
|
||||
*/
|
||||
public function getUserSshKeys(?User $user): ?array
|
||||
{
|
||||
if (is_null($user)) {
|
||||
try {
|
||||
if (is_null($user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->cp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$vfUser = $this->getVFUserDetails($user['id']);
|
||||
|
||||
if (! $vfUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$response = $request->get(
|
||||
sprintf('%s/ssh_keys/user/%d', $this->cp['url'], $vfUser['id']),
|
||||
);
|
||||
|
||||
return $this->decodeResponseFromJson($response);
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->cp) return null;
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$vfUser = $this->getVFUserDetails($user['id']);
|
||||
|
||||
if (!$vfUser) return null;
|
||||
|
||||
$response = $request->get(
|
||||
sprintf("%s/ssh_keys/user/%d", $this->cp['url'], $vfUser['id'])
|
||||
);
|
||||
|
||||
return $this->decodeResponseFromJson($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return array|null
|
||||
* @throws JsonException
|
||||
* Look up a VirtFusion user by WHMCS external relation ID.
|
||||
*
|
||||
* Calls the VirtFusion API's byExtRelation endpoint using the WHMCS client
|
||||
* ID. Returns null if the user does not exist in VirtFusion or no control
|
||||
* panel is available.
|
||||
*
|
||||
* @param int $id WHMCS client ID used as the VirtFusion external relation ID.
|
||||
* @return array|null VirtFusion user data array, or null if not found.
|
||||
*/
|
||||
public function getVFUserDetails(int $id): ?array
|
||||
{
|
||||
if (!$this->cp) return null;
|
||||
try {
|
||||
if (! $this->cp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$response = $this->decodeResponseFromJson($request->get(
|
||||
sprintf("%s/users/%d/byExtRelation", $this->cp['url'], $id)
|
||||
));
|
||||
$response = $this->decodeResponseFromJson($request->get(
|
||||
sprintf('%s/users/%d/byExtRelation', $this->cp['url'], $id),
|
||||
));
|
||||
|
||||
return isset($response['msg']) && $response['msg'] === "ext_relation_id not found" ? null : $response['data'];
|
||||
return isset($response['msg']) && $response['msg'] === 'ext_relation_id not found' ? null : $response['data'];
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param array $vars
|
||||
* @param int|null $vfUserId VirtFusion user ID (for creating SSH keys from raw public key)
|
||||
* @return bool
|
||||
* Trigger OS installation on a newly created VirtFusion server.
|
||||
*
|
||||
* Posts a build request to the VirtFusion API with the selected OS template
|
||||
* and optionally an SSH key. If the custom field contains a numeric value it
|
||||
* is treated as an existing key ID; if it is a raw public key string, the key
|
||||
* is created first via createUserSshKey(). Returns true on HTTP 200/201.
|
||||
*
|
||||
* @param int $id VirtFusion server ID to build.
|
||||
* @param array $vars WHMCS order vars, including customfields for OS and SSH key.
|
||||
* @param int|null $vfUserId VirtFusion user ID, required when creating a new SSH key from a raw public key.
|
||||
* @return bool True if the build request was accepted, false otherwise.
|
||||
*/
|
||||
public function initServerBuild(int $id, array $vars, ?int $vfUserId = null): bool
|
||||
{
|
||||
if (!$this->cp) return false;
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
// Generate a hostname with sufficient entropy to avoid collisions
|
||||
$hostname = 'vps-' . bin2hex(random_bytes(4));
|
||||
|
||||
$sshKeyValue = $vars['customfields']['Initial SSH Key'] ?? null;
|
||||
$sshKeyId = null;
|
||||
|
||||
if (!empty($sshKeyValue)) {
|
||||
if (is_numeric($sshKeyValue)) {
|
||||
// Existing SSH key ID
|
||||
$sshKeyId = (int) $sshKeyValue;
|
||||
} elseif (preg_match('/^ssh-/', $sshKeyValue) && $vfUserId) {
|
||||
// Raw public key — create it via API
|
||||
$sshKeyId = $this->createUserSshKey($vfUserId, $sshKeyValue);
|
||||
try {
|
||||
if (! $this->cp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
// Generate a hostname with sufficient entropy to avoid collisions
|
||||
$hostname = 'vps-' . bin2hex(random_bytes(4));
|
||||
|
||||
$sshKeyValue = $vars['customfields']['Initial SSH Key'] ?? null;
|
||||
$sshKeyId = null;
|
||||
|
||||
if (! empty($sshKeyValue)) {
|
||||
if (is_numeric($sshKeyValue)) {
|
||||
// Existing SSH key ID
|
||||
$sshKeyId = (int) $sshKeyValue;
|
||||
} elseif (preg_match('/^ssh-/', $sshKeyValue) && $vfUserId) {
|
||||
// Raw public key — create it via API
|
||||
$sshKeyId = $this->createUserSshKey($vfUserId, $sshKeyValue);
|
||||
}
|
||||
}
|
||||
|
||||
$inputData = [
|
||||
'operatingSystemId' => $vars['customfields']['Initial Operating System'] ?? null,
|
||||
'name' => $hostname,
|
||||
'email' => true,
|
||||
];
|
||||
|
||||
if ($sshKeyId) {
|
||||
$inputData['sshKeys'] = [$sshKeyId];
|
||||
}
|
||||
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode($inputData));
|
||||
|
||||
$response = $request->post(
|
||||
sprintf('%s/servers/%d/build', $this->cp['url'], $id),
|
||||
);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $response);
|
||||
|
||||
return $httpCode == 200 || $httpCode == 201;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$inputData = [
|
||||
"operatingSystemId" => $vars['customfields']['Initial Operating System'] ?? null,
|
||||
"name" => $hostname,
|
||||
'email' => true
|
||||
];
|
||||
|
||||
if ($sshKeyId) {
|
||||
$inputData['sshKeys'] = [$sshKeyId];
|
||||
}
|
||||
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode($inputData));
|
||||
|
||||
$response = $request->post(
|
||||
sprintf("%s/servers/%d/build", $this->cp['url'], $id)
|
||||
);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $response);
|
||||
|
||||
return ($httpCode == 200 || $httpCode == 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SSH key for a VirtFusion user from a raw public key string.
|
||||
*
|
||||
* @param int $userId VirtFusion user ID
|
||||
* @param string $publicKey Raw SSH public key (ssh-rsa ..., ssh-ed25519 ..., etc.)
|
||||
* @param int $userId VirtFusion user ID
|
||||
* @param string $publicKey Raw SSH public key (ssh-rsa ..., ssh-ed25519 ..., etc.)
|
||||
* @return int|null Created key ID or null on failure
|
||||
*/
|
||||
public function createUserSshKey(int $userId, string $publicKey): ?int
|
||||
{
|
||||
if (!$this->cp) return null;
|
||||
try {
|
||||
if (! $this->cp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
$request = $this->initCurl($this->cp['token']);
|
||||
|
||||
$keyData = [
|
||||
'userId' => $userId,
|
||||
'name' => 'WHMCS-' . date('Y-m-d'),
|
||||
'publicKey' => trim($publicKey),
|
||||
];
|
||||
$keyData = [
|
||||
'userId' => $userId,
|
||||
'name' => 'WHMCS-' . date('Y-m-d'),
|
||||
'publicKey' => trim($publicKey),
|
||||
];
|
||||
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode($keyData));
|
||||
$response = $request->post($this->cp['url'] . '/ssh_keys');
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode($keyData));
|
||||
$response = $request->post($this->cp['url'] . '/ssh_keys');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $response);
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $response);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 201) {
|
||||
$data = json_decode($response, true);
|
||||
return $data['data']['id'] ?? null;
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 201) {
|
||||
$data = json_decode($response, true);
|
||||
|
||||
return $data['data']['id'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,22 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* HTTP client wrapper with Bearer token auth, SSL verification, and a 30s timeout.
|
||||
* Single-use — each instance makes one request.
|
||||
*/
|
||||
class Curl
|
||||
{
|
||||
/** @var resource|\CurlHandle cURL handle */
|
||||
private $ch;
|
||||
|
||||
/** @var array Response info and parsed header data collected after exec */
|
||||
private $data;
|
||||
|
||||
/** @var array User-supplied cURL options that override defaults */
|
||||
private $customOptions = [];
|
||||
|
||||
/** @var array Default cURL options applied to every request */
|
||||
private $defaultOptions = [
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
@@ -18,15 +29,17 @@ class Curl
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
];
|
||||
|
||||
|
||||
/** Initialise the cURL handle. */
|
||||
public function __construct()
|
||||
{
|
||||
$this->ch = curl_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $value
|
||||
* Set a custom cURL option, overriding the defaults.
|
||||
*
|
||||
* @param int $name A CURLOPT_* constant
|
||||
* @param mixed $value The option value
|
||||
*/
|
||||
public function addOption($name, $value)
|
||||
{
|
||||
@@ -34,8 +47,10 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $url
|
||||
* @return bool|string|void
|
||||
* Execute a PUT request.
|
||||
*
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*/
|
||||
public function put($url = null)
|
||||
{
|
||||
@@ -43,8 +58,10 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $url
|
||||
* @return bool|string|void
|
||||
* Execute a PATCH request.
|
||||
*
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*/
|
||||
public function patch($url = null)
|
||||
{
|
||||
@@ -52,14 +69,18 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param $url
|
||||
* @return bool|string|void
|
||||
* Set the HTTP method and URL, then execute the request.
|
||||
*
|
||||
* @param string $method HTTP method (GET, POST, PUT, PATCH, DELETE)
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*
|
||||
* @throws \RuntimeException If no URL is available
|
||||
*/
|
||||
private function send($method, $url)
|
||||
{
|
||||
if ($url === null) {
|
||||
if (!isset($this->customOptions[CURLOPT_URL]) || empty($this->customOptions[CURLOPT_URL])) {
|
||||
if (! isset($this->customOptions[CURLOPT_URL]) || empty($this->customOptions[CURLOPT_URL])) {
|
||||
throw new \RuntimeException('Curl: empty URL provided');
|
||||
}
|
||||
}
|
||||
@@ -70,7 +91,9 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|string
|
||||
* Apply options, run the cURL handle, collect response info, and close the handle.
|
||||
*
|
||||
* @return bool|string Response body, or false on cURL error
|
||||
*/
|
||||
private function exec()
|
||||
{
|
||||
@@ -94,6 +117,7 @@ class Curl
|
||||
return $response;
|
||||
}
|
||||
|
||||
/** Merge custom and default cURL options and apply them to the handle. */
|
||||
private function setOptions()
|
||||
{
|
||||
if (isset($this->customOptions[CURLOPT_HEADER]) && $this->customOptions[CURLOPT_HEADER]) {
|
||||
@@ -105,7 +129,9 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* Split a response containing headers into header and body parts and store them.
|
||||
*
|
||||
* @param string $data Raw response string (headers + body); replaced with body only
|
||||
*/
|
||||
private function processHeaders(&$data)
|
||||
{
|
||||
@@ -116,15 +142,17 @@ class Curl
|
||||
|
||||
$tmp = explode("\r\n", $this->data['info']['response_header']);
|
||||
$this->data['data']['Message'] = $tmp[0];
|
||||
for ($i = 1, $size = count($tmp); $i < $size; ++$i) {
|
||||
for ($i = 1, $size = count($tmp); $i < $size; $i++) {
|
||||
$string = explode(': ', $tmp[$i], 2);
|
||||
$this->data['data'][$string[0]] = $string[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $url
|
||||
* @return bool|string|void
|
||||
* Execute a GET request.
|
||||
*
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*/
|
||||
public function get($url = null)
|
||||
{
|
||||
@@ -132,8 +160,10 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $url
|
||||
* @return bool|string|void
|
||||
* Execute a DELETE request.
|
||||
*
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*/
|
||||
public function delete($url = null)
|
||||
{
|
||||
@@ -141,8 +171,10 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $url
|
||||
* @return bool|string|void
|
||||
* Execute a POST request.
|
||||
*
|
||||
* @param string|null $url Target URL, or null to use a previously set CURLOPT_URL
|
||||
* @return bool|string Response body, or false on failure
|
||||
*/
|
||||
public function post($url = null)
|
||||
{
|
||||
@@ -150,8 +182,10 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param false $param
|
||||
* @return mixed|null
|
||||
* Return curl_getinfo data for the completed request.
|
||||
*
|
||||
* @param string|false $param A specific info key to retrieve, or false for the full array
|
||||
* @return mixed|null The requested info value, the full info array, or null if the key is absent
|
||||
*/
|
||||
public function getRequestInfo($param = false)
|
||||
{
|
||||
@@ -163,9 +197,11 @@ class Curl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $what
|
||||
* @param $name
|
||||
* @return mixed|null
|
||||
* Retrieve a single item from the internal data store by section and key.
|
||||
*
|
||||
* @param string $what Top-level section key (e.g. 'info', 'data')
|
||||
* @param string $name Item key within that section
|
||||
* @return mixed|null The stored value, or null if not found
|
||||
*/
|
||||
private function getDataItem($what, $name)
|
||||
{
|
||||
@@ -175,5 +211,4 @@ class Curl
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,13 +4,28 @@ namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
use WHMCS\Database\Capsule as DB;
|
||||
|
||||
/**
|
||||
* Handles all database operations for the module's custom table (mod_virtfusion_direct)
|
||||
* and queries against core WHMCS tables (tblhosting, tblclients, tblservers, etc.).
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
const SYSTEM_TABLE = 'mod_virtfusion_direct';
|
||||
|
||||
/** @var bool Tracks whether custom field existence has already been verified this request. */
|
||||
private static $fieldsChecked = false;
|
||||
|
||||
/**
|
||||
* Creates or migrates the module table schema and ensures custom fields exist.
|
||||
*
|
||||
* Creates mod_virtfusion_direct with service_id and server_id columns if absent,
|
||||
* adds the server_object column if missing, then calls ensureCustomFields().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function schema()
|
||||
{
|
||||
if (!DB::schema()->hasTable(self::SYSTEM_TABLE)) {
|
||||
if (! DB::schema()->hasTable(self::SYSTEM_TABLE)) {
|
||||
try {
|
||||
DB::schema()->create(self::SYSTEM_TABLE, function ($table) {
|
||||
$table->unsignedBigInteger('service_id')->nullable()->default(null)->index();
|
||||
@@ -22,7 +37,7 @@ class Database
|
||||
}
|
||||
}
|
||||
|
||||
if (!DB::schema()->hasColumn(self::SYSTEM_TABLE, 'server_object')) {
|
||||
if (! DB::schema()->hasColumn(self::SYSTEM_TABLE, 'server_object')) {
|
||||
try {
|
||||
DB::schema()->table(self::SYSTEM_TABLE, function ($table) {
|
||||
$table->longText('server_object')->nullable()->default(null);
|
||||
@@ -31,92 +46,283 @@ class Database
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
self::ensureCustomFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the "Initial Operating System" and "Initial SSH Key" custom fields exist
|
||||
* for every VirtFusionDirect product, creating them via upsert if absent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ensureCustomFields()
|
||||
{
|
||||
if (self::$fieldsChecked) {
|
||||
return;
|
||||
}
|
||||
self::$fieldsChecked = true;
|
||||
|
||||
try {
|
||||
$productIds = DB::table('tblproducts')
|
||||
->where('servertype', 'VirtFusionDirect')
|
||||
->pluck('id');
|
||||
|
||||
foreach ($productIds as $productId) {
|
||||
foreach (['Initial Operating System', 'Initial SSH Key'] as $fieldName) {
|
||||
DB::table('tblcustomfields')->updateOrInsert(
|
||||
['type' => 'product', 'relid' => $productId, 'fieldname' => $fieldName],
|
||||
[
|
||||
'fieldtype' => 'text',
|
||||
'description' => '',
|
||||
'fieldoptions' => '',
|
||||
'regexpr' => '',
|
||||
'adminonly' => '',
|
||||
'required' => '',
|
||||
'showorder' => 'on',
|
||||
'showinvoice' => '',
|
||||
'sortorder' => 0,
|
||||
'updated_at' => DB::raw('UTC_TIMESTAMP()'),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a VirtFusionDirect server record from tblservers.
|
||||
*
|
||||
* When $server is non-zero, returns the matching server by ID.
|
||||
* When $any is true and $server is 0, returns the first enabled server.
|
||||
*
|
||||
* @param int $server WHMCS server ID to look up (0 to skip ID filter).
|
||||
* @param bool $any If true, fall back to the first active server.
|
||||
* @return object|false Row object on success, false on failure or not found.
|
||||
*/
|
||||
public static function getWhmcsServer(int $server, $any = false)
|
||||
{
|
||||
if ($server) {
|
||||
return DB::table('tblservers')->where('type', 'VirtFusionDirect')->where('id', $server)->first();
|
||||
}
|
||||
try {
|
||||
if ($server) {
|
||||
return DB::table('tblservers')->where('type', 'VirtFusionDirect')->where('id', $server)->first();
|
||||
}
|
||||
|
||||
if ($any) {
|
||||
return DB::table('tblservers')->where('type', 'VirtFusionDirect')->where('disabled', 0)->first();
|
||||
if ($any) {
|
||||
return DB::table('tblservers')->where('type', 'VirtFusionDirect')->where('disabled', 0)->first();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a WHMCS service belongs to the given client.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @param int $userId WHMCS client ID.
|
||||
* @return bool True if the service is owned by the client, false otherwise.
|
||||
*/
|
||||
public static function userWhmcsService(int $serviceId, int $userId)
|
||||
{
|
||||
return DB::table('tblhosting')->where('id', $serviceId)->where('userid', $userId)->exists();
|
||||
try {
|
||||
return DB::table('tblhosting')->where('id', $serviceId)->where('userid', $userId)->exists();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WHMCS system URL from tblconfiguration.
|
||||
*
|
||||
* @return string The system URL, or an empty string if not found or on error.
|
||||
*/
|
||||
public static function getSystemUrl()
|
||||
{
|
||||
$url = DB::table('tblconfiguration')->where('setting', '=', 'SystemURL')->first();
|
||||
if (!$url) return '';
|
||||
return $url->value;
|
||||
try {
|
||||
$url = DB::table('tblconfiguration')->where('setting', '=', 'SystemURL')->first();
|
||||
if (! $url) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $url->value;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a WHMCS client record by ID.
|
||||
*
|
||||
* @param int $id WHMCS client ID.
|
||||
* @return object|null Row object on success, null on failure or not found.
|
||||
*/
|
||||
public static function getUser(int $id)
|
||||
{
|
||||
return DB::table('tblclients')->where('id', $id)->first();
|
||||
try {
|
||||
return DB::table('tblclients')->where('id', $id)->first();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a WHMCS hosting service record by ID.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @return object|null Row object on success, null on failure or not found.
|
||||
*/
|
||||
public static function getWhmcsService(int $serviceId)
|
||||
{
|
||||
return DB::table('tblhosting')->where('id', $serviceId)->first();
|
||||
try {
|
||||
return DB::table('tblhosting')->where('id', $serviceId)->first();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts the VirtFusion server ID for a given WHMCS service in the module table.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @param int $serverId VirtFusion server ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function updateSystemServiceServerId(int $serviceId, int $serverId)
|
||||
{
|
||||
|
||||
DB::table(self::SYSTEM_TABLE)->updateOrInsert(
|
||||
[
|
||||
"service_id" => $serviceId
|
||||
],
|
||||
[
|
||||
'server_id' => $serverId
|
||||
]
|
||||
);
|
||||
try {
|
||||
DB::table(self::SYSTEM_TABLE)->updateOrInsert(
|
||||
[
|
||||
'service_id' => $serviceId,
|
||||
],
|
||||
[
|
||||
'server_id' => $serverId,
|
||||
],
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one or more WHMCS tables with the provided data for a given service ID.
|
||||
*
|
||||
* $data is keyed by table name; each value is an associative array of column => value
|
||||
* pairs passed to an update() WHERE id = $serviceId.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @param array $data Map of table name to column-value pairs to update.
|
||||
* @return void
|
||||
*/
|
||||
public static function updateWhmcsServiceParams(int $serviceId, $data)
|
||||
{
|
||||
if (count($data)) {
|
||||
foreach ($data as $key => $items) {
|
||||
DB::table($key)->where('id', $serviceId)->update($items);
|
||||
try {
|
||||
if (count($data)) {
|
||||
foreach ($data as $key => $items) {
|
||||
DB::table($key)->where('id', $serviceId)->update($items);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a module table record exists for the given service.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @return bool True if a record exists, false otherwise.
|
||||
*/
|
||||
public static function checkSystemService(int $serviceId)
|
||||
{
|
||||
return DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->exists();
|
||||
}
|
||||
try {
|
||||
return DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->exists();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
public static function deleteSystemService(int $serviceId)
|
||||
{
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->delete();
|
||||
}
|
||||
|
||||
public static function updateSystemServiceServerObject(int $serviceId, $data)
|
||||
{
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->update(['server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
}
|
||||
|
||||
public static function systemOnServerCreate(int $serviceId, $data)
|
||||
{
|
||||
if (DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->exists()) {
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->update(['server_id' => $data->data->id, 'server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
} else {
|
||||
DB::table(self::SYSTEM_TABLE)->insert(['service_id' => $serviceId, 'server_id' => $data->data->id, 'server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getSystemService(int $serviceId)
|
||||
/**
|
||||
* Deletes the module table record for the given service.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteSystemService(int $serviceId)
|
||||
{
|
||||
return DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->first();
|
||||
try {
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->delete();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Persists the raw VirtFusion server API response as JSON in the module table.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @param mixed $data Server object from the VirtFusion API (will be JSON-encoded).
|
||||
* @return void
|
||||
*/
|
||||
public static function updateSystemServiceServerObject(int $serviceId, $data)
|
||||
{
|
||||
try {
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->update(['server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates the module table record immediately after a VirtFusion server is created.
|
||||
*
|
||||
* Stores both the VirtFusion server ID (from $data->data->id) and the full server
|
||||
* object JSON. Uses update if a record already exists, otherwise inserts.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @param mixed $data Full API response object from the VirtFusion server creation call.
|
||||
* @return void
|
||||
*/
|
||||
public static function systemOnServerCreate(int $serviceId, $data)
|
||||
{
|
||||
try {
|
||||
if (DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->exists()) {
|
||||
DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->update(['server_id' => $data->data->id, 'server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
} else {
|
||||
DB::table(self::SYSTEM_TABLE)->insert(['service_id' => $serviceId, 'server_id' => $data->data->id, 'server_object' => json_encode($data, JSON_PRETTY_PRINT)]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the module table record for the given service.
|
||||
*
|
||||
* @param int $serviceId WHMCS hosting service ID.
|
||||
* @return object|null Row object on success, null on failure or not found.
|
||||
*/
|
||||
public static function getSystemService(int $serviceId)
|
||||
{
|
||||
try {
|
||||
return DB::table(self::SYSTEM_TABLE)->where('service_id', $serviceId)->first();
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, [], $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Thin wrapper around the WHMCS logModuleCall() function for module-level logging.
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
const LOG_MODULE = 'VirtFusionDirect';
|
||||
|
||||
/**
|
||||
* Write an entry to the WHMCS module log.
|
||||
*
|
||||
* @param string $action Name of the action being logged (e.g. 'CreateAccount')
|
||||
* @param string|array $requestString Request data sent to the API
|
||||
* @param string|array $responseData Response data received from the API
|
||||
*/
|
||||
public static function insert($action, $requestString, $responseData)
|
||||
{
|
||||
/**
|
||||
* Log module call.
|
||||
*
|
||||
* @param string $module The name of the module
|
||||
* @param string $action The name of the action being performed
|
||||
* @param string|array $requestString The input parameters for the API call
|
||||
* @param string|array $responseData The response data from the API call
|
||||
* @param string|array $processedData The resulting data after any post processing (eg. json decode, xml decode, etc...)
|
||||
* @param array $replaceVars An array of strings for replacement
|
||||
*/
|
||||
logModuleCall(self::LOG_MODULE, $action, $requestString, $responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Extends Module to handle the WHMCS service lifecycle for VirtFusion servers.
|
||||
*
|
||||
* Responsibilities include: provisioning (create, suspend, unsuspend, terminate),
|
||||
* package changes, usage updates, client area rendering, and admin tab fields.
|
||||
*/
|
||||
class ModuleFunctions extends Module
|
||||
{
|
||||
public function __construct()
|
||||
@@ -10,13 +16,13 @@ class ModuleFunctions extends Module
|
||||
}
|
||||
|
||||
/**
|
||||
* Provision a new VirtFusion server for a WHMCS service.
|
||||
*
|
||||
* CREATE SERVER
|
||||
*
|
||||
* Before creating a server, we check to see if a user exists in VirtFusion that matches
|
||||
* the WHMCS user. If it matches, We move on to create the server, if not, we attempt to
|
||||
* create a user to assign to the new server.
|
||||
* Ensures a matching VirtFusion user exists (creating one if needed), then creates
|
||||
* the server and triggers the OS build via ConfigureService::initServerBuild().
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function createAccount($params)
|
||||
{
|
||||
@@ -33,9 +39,9 @@ class ModuleFunctions extends Module
|
||||
* If no VirtFusionDirect control server exists, cancel the create account action.
|
||||
*/
|
||||
$server = $params['serverid'] ?: false;
|
||||
$cp = $this->getCP($server, !$server);
|
||||
$cp = $this->getCP($server, ! $server);
|
||||
|
||||
if (!$cp) {
|
||||
if (! $cp) {
|
||||
return 'No Control server found. Please ensure a VirtFusion server is configured in WHMCS.';
|
||||
}
|
||||
|
||||
@@ -62,16 +68,16 @@ class ModuleFunctions extends Module
|
||||
*/
|
||||
$user = Database::getUser($params['userid']);
|
||||
|
||||
if (!$user) {
|
||||
if (! $user) {
|
||||
return 'WHMCS user not found for ID ' . (int) $params['userid'];
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
|
||||
$userData = [
|
||||
"name" => $user->firstname . ' ' . $user->lastname,
|
||||
"email" => $user->email,
|
||||
"extRelationId" => $user->id,
|
||||
'name' => $user->firstname . ' ' . $user->lastname,
|
||||
'email' => $user->email,
|
||||
'extRelationId' => $user->id,
|
||||
];
|
||||
|
||||
// Enable self-service billing if configured
|
||||
@@ -100,7 +106,6 @@ class ModuleFunctions extends Module
|
||||
/**
|
||||
* A user is available. We can now attempt to create a server.
|
||||
*/
|
||||
|
||||
$configOptionDefaultNaming = [
|
||||
'ipv4' => 'IPv4',
|
||||
'packageId' => 'Package',
|
||||
@@ -122,10 +127,10 @@ class ModuleFunctions extends Module
|
||||
}
|
||||
|
||||
$options = [
|
||||
"packageId" => (int) $params['configoption2'],
|
||||
"userId" => $data->data->id,
|
||||
"hypervisorId" => (int) $params['configoption1'],
|
||||
"ipv4" => (int) $params['configoption3'],
|
||||
'packageId' => (int) $params['configoption2'],
|
||||
'userId' => $data->data->id,
|
||||
'hypervisorId' => (int) $params['configoption1'],
|
||||
'ipv4' => (int) $params['configoption3'],
|
||||
];
|
||||
|
||||
if (array_key_exists('configoptions', $params)) {
|
||||
@@ -159,7 +164,7 @@ class ModuleFunctions extends Module
|
||||
$this->updateWhmcsServiceParamsOnServerObject($params['serviceid'], $data);
|
||||
|
||||
// If the server is created successfully, we can initialize the server build.
|
||||
$cs = new ConfigureService();
|
||||
$cs = new ConfigureService;
|
||||
$vfUserId = isset($data->data->owner->id) ? (int) $data->data->owner->id : null;
|
||||
$cs->initServerBuild($data->data->id, $params, $vfUserId);
|
||||
|
||||
@@ -171,328 +176,440 @@ class ModuleFunctions extends Module
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
|
||||
return 'Server creation failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows changing of the package of a server
|
||||
* Change the VirtFusion package assigned to a server and apply resource modifications.
|
||||
*
|
||||
* @param $params
|
||||
* @return string
|
||||
* Updates the package via the API, then individually adjusts memory, CPU, and bandwidth
|
||||
* if those configurable options are present.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function changePackage($params)
|
||||
{
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
try {
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (!$whmcsService) return 'WHMCS service record not found.';
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return 'No control server found.';
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->put($cp['url'] . '/servers/' . (int) $service->server_id . '/package/' . (int) $params['configoption2']);
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
break;
|
||||
case 404:
|
||||
return 'The server or package was not found in VirtFusion (HTTP 404).';
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
default:
|
||||
return 'Update package request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
|
||||
// Apply individual resource modifications from configurable options
|
||||
if (isset($params['configoptions']) && is_array($params['configoptions'])) {
|
||||
$configOptionDefaultNaming = [
|
||||
'memory' => 'Memory',
|
||||
'cpuCores' => 'CPU Cores',
|
||||
'traffic' => 'Bandwidth',
|
||||
];
|
||||
|
||||
$configOptionCustomNaming = [];
|
||||
if (file_exists(ROOTDIR . '/modules/servers/VirtFusionDirect/config/ConfigOptionMapping.php')) {
|
||||
$configOptionCustomNaming = require ROOTDIR . '/modules/servers/VirtFusionDirect/config/ConfigOptionMapping.php';
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (! $whmcsService) {
|
||||
return 'WHMCS service record not found.';
|
||||
}
|
||||
|
||||
foreach ($configOptionDefaultNaming as $resource => $optionName) {
|
||||
$currentOption = array_key_exists($resource, $configOptionCustomNaming) ? $configOptionCustomNaming[$resource] : $optionName;
|
||||
if (isset($params['configoptions'][$currentOption]) && is_numeric($params['configoptions'][$currentOption])) {
|
||||
$value = (int) $params['configoptions'][$currentOption];
|
||||
if ($resource === 'memory' && $value < 1024) {
|
||||
$value = $value * 1024;
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
return 'No control server found.';
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->put($cp['url'] . '/servers/' . (int) $service->server_id . '/package/' . (int) $params['configoption2']);
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
break;
|
||||
case 404:
|
||||
return 'The server or package was not found in VirtFusion (HTTP 404).';
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
default:
|
||||
return 'Update package request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
|
||||
// Apply individual resource modifications from configurable options
|
||||
if (isset($params['configoptions']) && is_array($params['configoptions'])) {
|
||||
$configOptionDefaultNaming = [
|
||||
'memory' => 'Memory',
|
||||
'cpuCores' => 'CPU Cores',
|
||||
'traffic' => 'Bandwidth',
|
||||
];
|
||||
|
||||
$configOptionCustomNaming = [];
|
||||
if (file_exists(ROOTDIR . '/modules/servers/VirtFusionDirect/config/ConfigOptionMapping.php')) {
|
||||
$configOptionCustomNaming = require ROOTDIR . '/modules/servers/VirtFusionDirect/config/ConfigOptionMapping.php';
|
||||
}
|
||||
|
||||
foreach ($configOptionDefaultNaming as $resource => $optionName) {
|
||||
$currentOption = array_key_exists($resource, $configOptionCustomNaming) ? $configOptionCustomNaming[$resource] : $optionName;
|
||||
if (isset($params['configoptions'][$currentOption]) && is_numeric($params['configoptions'][$currentOption])) {
|
||||
$value = (int) $params['configoptions'][$currentOption];
|
||||
if ($resource === 'memory' && $value < 1024) {
|
||||
$value = $value * 1024;
|
||||
}
|
||||
$this->modifyResource($params['serviceid'], $resource, $value);
|
||||
}
|
||||
$this->modifyResource($params['serviceid'], $resource, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return 'success';
|
||||
return 'Service not found in module database.';
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
return 'Service not found in module database.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a VirtFusion server, applying the default 5-minute grace period before destruction.
|
||||
*
|
||||
* TERMINATE SERVER
|
||||
*
|
||||
* When requesting to terminate a server in VirtFusion, we leave it set to
|
||||
* the default 5-minute delay allowing to un-terminate in VirtFusion if the
|
||||
* request was done in error.
|
||||
* On success, removes the service record from the module database and clears WHMCS service fields.
|
||||
* If VirtFusion reports the server is already gone (404 + "server not found"), treats it as success.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function terminateAccount($params)
|
||||
{
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
try {
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
if ($service) {
|
||||
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (!$whmcsService) return 'WHMCS service record not found.';
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (! $whmcsService) {
|
||||
return 'WHMCS service record not found.';
|
||||
}
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return 'No control server found.';
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
return 'No control server found.';
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id);
|
||||
$data = json_decode($data);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id);
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
$this->updateWhmcsServiceParamsOnDestroy($params['serviceid']);
|
||||
return 'success';
|
||||
case 204:
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
$this->updateWhmcsServiceParamsOnDestroy($params['serviceid']);
|
||||
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
return 'success';
|
||||
return 'success';
|
||||
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
|
||||
return 'success';
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
|
||||
default:
|
||||
return 'Termination request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
default:
|
||||
return 'Termination request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
|
||||
return 'Service not found in module database. Has termination already been run?';
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
return 'Service not found in module database. Has termination already been run?';
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend a VirtFusion server, queuing the action if another operation is in progress.
|
||||
*
|
||||
* SUSPEND SERVER
|
||||
*
|
||||
* When requesting to suspend a server in VirtFusion it may be delayed if another action
|
||||
* is being processed. This function will return success if the server is either suspended
|
||||
* now or has been queued for suspension.
|
||||
* Returns 'success' whether the server is suspended immediately or queued for suspension.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function suspendAccount($params)
|
||||
{
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
try {
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
if ($service) {
|
||||
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (!$whmcsService) return 'WHMCS service record not found.';
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (! $whmcsService) {
|
||||
return 'WHMCS service record not found.';
|
||||
}
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return 'No control server found.';
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
return 'No control server found.';
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/suspend');
|
||||
$data = json_decode($data);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/suspend');
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
return 'success';
|
||||
case 204:
|
||||
return 'success';
|
||||
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
return 'success';
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
|
||||
return 'success';
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
|
||||
default:
|
||||
return 'Suspend request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
return 'Service not found in module database.';
|
||||
}
|
||||
|
||||
function updateServerObject($params)
|
||||
{
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (!$whmcsService) return 'WHMCS service record not found.';
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return 'No control server found.';
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id);
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 200:
|
||||
Database::updateSystemServiceServerObject($params['serviceid'], $data);
|
||||
|
||||
$this->updateWhmcsServiceParamsOnServerObject($params['serviceid'], $data);
|
||||
|
||||
return 'success';
|
||||
default:
|
||||
return 'Request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
return 'Service not found in module database.';
|
||||
}
|
||||
|
||||
|
||||
public function unsuspendAccount($params)
|
||||
{
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (!$whmcsService) return 'WHMCS service record not found.';
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return 'No control server found.';
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/unsuspend');
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
return 'success';
|
||||
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
return 'success';
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
|
||||
default:
|
||||
return 'Unsuspend request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
|
||||
default:
|
||||
return 'Suspend request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'Service not found in module database.';
|
||||
}
|
||||
|
||||
public function adminServicesTabFields($params)
|
||||
{
|
||||
$serverId = '';
|
||||
$serverObject = '';
|
||||
return 'Service not found in module database.';
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
$systemUrl = Database::getSystemUrl();
|
||||
|
||||
if ($service) {
|
||||
$serverId = $service->server_id;
|
||||
$serverObject = $service->server_object;
|
||||
}
|
||||
$fields = [
|
||||
'Server ID' => AdminHTML::serverId($serverId),
|
||||
'Server Info' => AdminHTML::serverInfo($systemUrl, $params['serviceid']),
|
||||
'Server Object' => AdminHTML::serverObject($serverObject),
|
||||
];
|
||||
|
||||
if ($params['status'] != 'Terminated') {
|
||||
$fields['Options'] = AdminHTML::options($systemUrl, $params['serviceid']);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function adminServicesTabFieldsSave($params)
|
||||
{
|
||||
if (!isset($_POST['modulefields'][0]) || $_POST['modulefields'][0] === '') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
} else {
|
||||
$serverId = (int) $_POST['modulefields'][0];
|
||||
if ($serverId > 0) {
|
||||
Database::updateSystemServiceServerId($params['serviceid'], $serverId);
|
||||
}
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate server creation parameters via dry run.
|
||||
* Refresh the cached server object by fetching fresh data from the VirtFusion API.
|
||||
*
|
||||
* @param array $params WHMCS service params
|
||||
* @return string 'success' or error message
|
||||
* Updates both the module database record and the WHMCS service fields (IP, username, etc.).
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function updateServerObject($params)
|
||||
{
|
||||
try {
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (! $whmcsService) {
|
||||
return 'WHMCS service record not found.';
|
||||
}
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
return 'No control server found.';
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id);
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 200:
|
||||
Database::updateSystemServiceServerObject($params['serviceid'], $data);
|
||||
|
||||
$this->updateWhmcsServiceParamsOnServerObject($params['serviceid'], $data);
|
||||
|
||||
return 'success';
|
||||
default:
|
||||
return 'Request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
|
||||
return 'Service not found in module database.';
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspend a VirtFusion server, queuing the action if another operation is in progress.
|
||||
*
|
||||
* Returns 'success' whether the server is unsuspended immediately or queued for unsuspension.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function unsuspendAccount($params)
|
||||
{
|
||||
try {
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($params['serviceid']);
|
||||
if (! $whmcsService) {
|
||||
return 'WHMCS service record not found.';
|
||||
}
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (! $cp) {
|
||||
return 'No control server found.';
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/unsuspend');
|
||||
$data = json_decode($data);
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
switch ($request->getRequestInfo('http_code')) {
|
||||
|
||||
case 204:
|
||||
return 'success';
|
||||
|
||||
case 404:
|
||||
if (isset($data->msg)) {
|
||||
if ($data->msg == 'server not found') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
|
||||
return 'success';
|
||||
} else {
|
||||
return 'VirtFusion returned 404: ' . $data->msg;
|
||||
}
|
||||
} else {
|
||||
return 'VirtFusion returned 404 without details. The API may be unavailable.';
|
||||
}
|
||||
case 423:
|
||||
if (isset($data->msg)) {
|
||||
return $data->msg;
|
||||
}
|
||||
|
||||
return 'The server is currently locked. Please try again later.';
|
||||
|
||||
default:
|
||||
return 'Unsuspend request failed. VirtFusion API returned HTTP ' . $request->getRequestInfo('http_code');
|
||||
}
|
||||
}
|
||||
|
||||
return 'Service not found in module database.';
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the admin Services tab custom fields for a VirtFusion service.
|
||||
*
|
||||
* Returns fields for Server ID (editable), Server Info, Server Object (JSON viewer),
|
||||
* and Options (action buttons), omitting Options for terminated services.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return array Associative array of field label => HTML content
|
||||
*/
|
||||
public function adminServicesTabFields($params)
|
||||
{
|
||||
try {
|
||||
$serverId = '';
|
||||
$serverObject = '';
|
||||
|
||||
$service = Database::getSystemService($params['serviceid']);
|
||||
$systemUrl = Database::getSystemUrl();
|
||||
|
||||
if ($service) {
|
||||
$serverId = $service->server_id;
|
||||
$serverObject = $service->server_object;
|
||||
}
|
||||
$fields = [
|
||||
'Server ID' => AdminHTML::serverId($serverId),
|
||||
'Server Info' => AdminHTML::serverInfo($systemUrl, $params['serviceid']),
|
||||
'Server Object' => AdminHTML::serverObject($serverObject),
|
||||
];
|
||||
|
||||
if ($params['status'] != 'Terminated') {
|
||||
$fields['Options'] = AdminHTML::options($systemUrl, $params['serviceid']);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the admin Services tab custom fields for a VirtFusion service.
|
||||
*
|
||||
* Deletes the module database record if the Server ID field is cleared,
|
||||
* or updates it with the new integer server ID if a value is provided.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return void
|
||||
*/
|
||||
public function adminServicesTabFieldsSave($params)
|
||||
{
|
||||
try {
|
||||
if (! isset($_POST['modulefields'][0]) || $_POST['modulefields'][0] === '') {
|
||||
Database::deleteSystemService($params['serviceid']);
|
||||
} else {
|
||||
$serverId = (int) $_POST['modulefields'][0];
|
||||
if ($serverId > 0) {
|
||||
Database::updateSystemServiceServerId($params['serviceid'], $serverId);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::insert(__FUNCTION__, $params, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a dry-run server creation to validate the current product configuration.
|
||||
*
|
||||
* Used by the WHMCS "Test Connection" button to confirm that the package, hypervisor,
|
||||
* and IP settings are accepted by the VirtFusion API without creating a server.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return string 'success' or an error message
|
||||
*/
|
||||
public function validateServerConfig($params)
|
||||
{
|
||||
try {
|
||||
$server = $params['serverid'] ?: false;
|
||||
$cp = $this->getCP($server, !$server);
|
||||
$cp = $this->getCP($server, ! $server);
|
||||
|
||||
if (!$cp) {
|
||||
if (! $cp) {
|
||||
return 'No Control server found.';
|
||||
}
|
||||
|
||||
$options = [
|
||||
"packageId" => (int) $params['configoption2'],
|
||||
"hypervisorId" => (int) $params['configoption1'],
|
||||
"ipv4" => (int) $params['configoption3'],
|
||||
'packageId' => (int) $params['configoption2'],
|
||||
'hypervisorId' => (int) $params['configoption1'],
|
||||
'ipv4' => (int) $params['configoption3'],
|
||||
];
|
||||
|
||||
// We need a userId for dry run - use the service owner
|
||||
@@ -517,6 +634,16 @@ class ModuleFunctions extends Module
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the client area overview tab for a VirtFusion service.
|
||||
*
|
||||
* Returns the template name and variables (system URL, service status, hostname,
|
||||
* self-service mode) needed by the Smarty overview template. Falls back to an
|
||||
* error template on any exception.
|
||||
*
|
||||
* @param array $params WHMCS service parameters
|
||||
* @return array Template name and variables for WHMCS to render
|
||||
*/
|
||||
public function clientArea($params)
|
||||
{
|
||||
$serverHostname = null;
|
||||
|
||||
@@ -2,8 +2,17 @@
|
||||
|
||||
namespace WHMCS\Module\Server\VirtFusionDirect;
|
||||
|
||||
/**
|
||||
* Transforms a VirtFusion API server response into a flat key-value array for Smarty templates and admin display.
|
||||
*/
|
||||
class ServerResource
|
||||
{
|
||||
/**
|
||||
* Normalise a VirtFusion API server response into a flat associative array.
|
||||
*
|
||||
* @param object $data VirtFusion API server response object (with a `data` property)
|
||||
* @return array Flat associative array containing server name, hostname, resources, network info, and usage
|
||||
*/
|
||||
public function process($data)
|
||||
{
|
||||
$server = json_decode(json_encode($data->data), true);
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
-- Insert records for Initial Operating System if they don't already exist
|
||||
INSERT INTO tblcustomfields
|
||||
(type, relid, fieldname, fieldtype, description, fieldoptions, regexpr, adminonly, required, showorder, showinvoice,
|
||||
sortorder, created_at, updated_at)
|
||||
SELECT 'product',
|
||||
id,
|
||||
'Initial Operating System',
|
||||
'text',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'on',
|
||||
'',
|
||||
0,
|
||||
UTC_TIMESTAMP(),
|
||||
UTC_TIMESTAMP()
|
||||
FROM tblproducts
|
||||
WHERE servertype = 'VirtFusionDirect'
|
||||
AND NOT EXISTS (SELECT 1
|
||||
FROM tblcustomfields
|
||||
WHERE fieldname = 'Initial Operating System'
|
||||
AND relid = tblproducts.id);
|
||||
|
||||
-- Insert records for Initial SSH Key if they don't already exist
|
||||
INSERT INTO tblcustomfields
|
||||
(type, relid, fieldname, fieldtype, description, fieldoptions, regexpr, adminonly, required, showorder, showinvoice,
|
||||
sortorder, created_at, updated_at)
|
||||
SELECT 'product',
|
||||
id,
|
||||
'Initial SSH Key',
|
||||
'text',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'on',
|
||||
'',
|
||||
0,
|
||||
UTC_TIMESTAMP(),
|
||||
UTC_TIMESTAMP()
|
||||
FROM tblproducts
|
||||
WHERE servertype = 'VirtFusionDirect'
|
||||
AND NOT EXISTS (SELECT 1
|
||||
FROM tblcustomfields
|
||||
WHERE fieldname = 'Initial SSH Key'
|
||||
AND relid = tblproducts.id);
|
||||
Reference in New Issue
Block a user