Enriches class-level docblocks and inline comments across the shared
utility classes with the "why" behind design decisions that aren't
obvious from reading the code alone:
- Cache two-tier rationale, atomic-write semantics, failure modes
- Curl single-use-per-instance rationale, default option choices
- Log wrapper rationale, redaction expectations for callers
- Database auto-migration philosophy, schema-versioning approach
- ServerResource flat-array rationale, interfaces[0]-only limit called
out for future maintainers, unit-conversion map
- ConfigureService why a sibling of ModuleFunctions, catalogue caching
policy, cp-in-constructor reasoning
Pure documentation — no code changes, all files remain lint-clean and
Pint-formatted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
7.6 KiB
PHP
144 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace WHMCS\Module\Server\VirtFusionDirect;
|
|
|
|
/**
|
|
* Transforms a VirtFusion API server response into a flat key-value array for Smarty templates and admin display.
|
|
*
|
|
* WHY A FLAT ARRAY
|
|
* ----------------
|
|
* Smarty templates can traverse nested structures (`{$data.network.interfaces[0].ipv4[0].address}`)
|
|
* but that leaks the API shape into the template layer. A flat array ("hostname",
|
|
* "primaryNetwork.ipv4[]", "memoryRaw", etc.) decouples the template from the upstream
|
|
* schema: if VirtFusion renames `network.interfaces` tomorrow, only this file needs
|
|
* to change.
|
|
*
|
|
* PRIMARY-INTERFACE-ONLY DESIGN
|
|
* -----------------------------
|
|
* process() only reads interfaces[0]. That's the primary network — the one the
|
|
* client-area "Overview" card displays. Servers with multiple interfaces (common
|
|
* for dedicated IPMI networks, storage networks, etc.) still work for display
|
|
* because the primary interface holds the customer-facing IP.
|
|
*
|
|
* The reverse-DNS subsystem (PowerDns\IpUtil::extractIps) walks ALL interfaces
|
|
* explicitly because PTRs matter for every IP no matter which NIC it's on.
|
|
* If you add a feature that needs secondary-interface data for display, do NOT
|
|
* generalise this class — add a new one or a helper that doesn't disturb the
|
|
* well-tested primary-interface behaviour.
|
|
*
|
|
* UNIT CONVERSIONS
|
|
* ----------------
|
|
* VirtFusion stores:
|
|
* - traffic as bytes (usage) or GB (limits)
|
|
* - storage as GB (limits) or bytes (usage)
|
|
* - memory as MB
|
|
* WHMCS expects MB for storage/traffic in tblhosting. This class produces two
|
|
* pairs of values per resource: a human-readable string with unit suffix
|
|
* (e.g. "200 GB") AND a raw integer without the unit (for slider UIs and
|
|
* arithmetic). Keep both — removing one breaks a UI consumer somewhere.
|
|
*
|
|
* "-" SENTINELS
|
|
* -------------
|
|
* Fields that are missing or empty are rendered as "-" rather than empty strings.
|
|
* That makes the client-area card always have content (a dash is a valid visual
|
|
* placeholder) and distinguishes "missing data" from "empty string returned by
|
|
* the API". Consumers who need boolean presence checks should test against "-",
|
|
* not "" / null — and upstream (e.g. updateWhmcsServiceParamsOnServerObject)
|
|
* already does.
|
|
*/
|
|
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);
|
|
|
|
$traffic = '-';
|
|
|
|
if (isset($server['settings']['resources']['traffic'])) {
|
|
if ($server['settings']['resources']['traffic'] > 0) {
|
|
$traffic = $server['settings']['resources']['traffic'] . ' GB';
|
|
} else {
|
|
$traffic = 'Unlimited';
|
|
}
|
|
}
|
|
|
|
$trafficUsed = '-';
|
|
if (isset($server['usage']['traffic']['used'])) {
|
|
$trafficUsed = round($server['usage']['traffic']['used'] / 1073741824, 2) . ' GB';
|
|
} elseif (isset($server['settings']['resources']['traffic']) && $server['settings']['resources']['traffic'] > 0) {
|
|
$trafficUsed = '0 GB';
|
|
}
|
|
|
|
$data = [
|
|
'name' => $server['name'] ?: '-',
|
|
'hostname' => $server['hostname'] ?: '-',
|
|
'memory' => isset($server['settings']['resources']['memory']) ? $server['settings']['resources']['memory'] . ' MB' : '-',
|
|
'traffic' => $traffic,
|
|
'trafficUsed' => $trafficUsed,
|
|
'storage' => isset($server['settings']['resources']['storage']) ? $server['settings']['resources']['storage'] . ' GB' : '-',
|
|
'cpu' => isset($server['settings']['resources']['cpuCores']) ? $server['settings']['resources']['cpuCores'] . ' Core(s)' : '-',
|
|
'status' => isset($server['state']) ? $server['state'] : 'unknown',
|
|
'powerStatus' => isset($server['hypervisor']['settings']['state']) ? $server['hypervisor']['settings']['state'] : 'unknown',
|
|
'username' => isset($server['owner']['email']) ? $server['owner']['email'] : '',
|
|
'password' => '',
|
|
'primaryNetwork' => [
|
|
'ipv4' => ['-'],
|
|
'ipv4Unformatted' => [],
|
|
'ipv6' => ['-'],
|
|
'ipv6Unformatted' => [],
|
|
'mac' => '-',
|
|
],
|
|
'networkSpeed' => [
|
|
'inbound' => isset($server['settings']['resources']['networkSpeedInbound']) ? $server['settings']['resources']['networkSpeedInbound'] . ' Mbps' : '-',
|
|
'outbound' => isset($server['settings']['resources']['networkSpeedOutbound']) ? $server['settings']['resources']['networkSpeedOutbound'] . ' Mbps' : '-',
|
|
],
|
|
'vncEnabled' => isset($server['vnc']['enabled']) ? (bool) $server['vnc']['enabled'] : false,
|
|
'memoryRaw' => isset($server['settings']['resources']['memory']) ? (int) $server['settings']['resources']['memory'] : 0,
|
|
'cpuRaw' => isset($server['settings']['resources']['cpuCores']) ? (int) $server['settings']['resources']['cpuCores'] : 0,
|
|
'storageRaw' => isset($server['settings']['resources']['storage']) ? (int) $server['settings']['resources']['storage'] : 0,
|
|
'trafficRaw' => isset($server['settings']['resources']['traffic']) ? (int) $server['settings']['resources']['traffic'] : 0,
|
|
'trafficUsedRaw' => isset($server['usage']['traffic']['used']) ? round($server['usage']['traffic']['used'] / 1073741824, 2) : 0,
|
|
'networkSpeedInboundRaw' => isset($server['settings']['resources']['networkSpeedInbound']) ? (int) $server['settings']['resources']['networkSpeedInbound'] : 0,
|
|
'networkSpeedOutboundRaw' => isset($server['settings']['resources']['networkSpeedOutbound']) ? (int) $server['settings']['resources']['networkSpeedOutbound'] : 0,
|
|
];
|
|
|
|
if (array_key_exists('network', $server)) {
|
|
if (array_key_exists('interfaces', $server['network'])) {
|
|
if (count($server['network']['interfaces'])) {
|
|
|
|
if (isset($server['network']['interfaces'][0]['mac'])) {
|
|
$data['primaryNetwork']['mac'] = $server['network']['interfaces'][0]['mac'];
|
|
}
|
|
|
|
if (isset($server['network']['interfaces'][0]['ipv4']) && count($server['network']['interfaces'][0]['ipv4'])) {
|
|
$data['primaryNetwork']['ipv4'] = [];
|
|
foreach ($server['network']['interfaces'][0]['ipv4'] as $ip) {
|
|
$data['primaryNetwork']['ipv4'][] = $ip['address'];
|
|
}
|
|
}
|
|
|
|
if (isset($server['network']['interfaces'][0]['ipv6']) && count($server['network']['interfaces'][0]['ipv6'])) {
|
|
$data['primaryNetwork']['ipv6'] = [];
|
|
foreach ($server['network']['interfaces'][0]['ipv6'] as $ip) {
|
|
$data['primaryNetwork']['ipv6'][] = $ip['subnet'] . '/' . $ip['cidr'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$data['primaryNetwork']['ipv4Unformatted'] = $data['primaryNetwork']['ipv4'];
|
|
$data['primaryNetwork']['ipv6Unformatted'] = $data['primaryNetwork']['ipv6'];
|
|
$data['primaryNetwork']['ipv4'] = implode(', ', $data['primaryNetwork']['ipv4']);
|
|
$data['primaryNetwork']['ipv6'] = implode(', ', $data['primaryNetwork']['ipv6']);
|
|
|
|
return $data;
|
|
}
|
|
}
|