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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user