chore: full project audit cleanup, dead code removal, and documentation update
Dead code removed: - Module.php: remove assignBackupPlan(), getSelfServiceCurrencies() (no callers) - Cache.php: remove forgetPattern() (no callers, no-op on filesystem) - module.js: remove vfLoadSelfServiceReport() (no UI trigger) Stale files removed: - .releaserc.json (orphaned, conflicts with tag-based workflow) - .github/workflows/api-sync-check.yml (baseline never populated) - docs/openapi-baseline.yaml (placeholder stub) - scripts/generate-endpoint-doc.sh (broken grep patterns) Security fixes: - AdminHTML: cast $serverId to (int), cast $serviceId to (int) - admin.php: add explicit break after every output() call, sanitize error msgs File hygiene: - Move modify.sql into modules/servers/VirtFusionDirect/ (matches README docs) - Fix CHANGELOG.md: remove duplicate 1.0.0 entry, clean up mixed git host URLs Documentation: - CLAUDE.md: full rewrite with current architecture, Cache class, development rules (try/catch, ownership validation, HTTP methods, caching policy) - README.md: remove stale IPv4 removal references, add new features (traffic, backups, VNC toggle, password reset, OS gallery, copy buttons), add Cache.php to file structure, remove "Primary IPv4 Protection" known issue Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,81 +13,71 @@ $vf->adminOnly();
|
||||
switch ($vf->validateAction(true)) {
|
||||
|
||||
/**
|
||||
*
|
||||
* Get server information.
|
||||
*
|
||||
*/
|
||||
case 'serverData':
|
||||
|
||||
if ($vf->validateServiceID(true)) {
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
/** No need to validate ownership **/
|
||||
|
||||
$whmcsService = Database::getWhmcsService((int)$_GET['serviceID']);
|
||||
|
||||
if (!$whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found.'], true, true, 404);
|
||||
}
|
||||
|
||||
if ($whmcsService->domainstatus == 'Pending' || $whmcsService->domainstatus == 'Terminated' || $whmcsService->domainstatus == 'Cancelled' || $whmcsService->domainstatus == 'Fraud') {
|
||||
$vf->output(['success' => false, 'errors' => 'Server is not Active, Suspended or Completed. Not fetching remote data.'], true, true, 400);
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData((int)$_GET['serviceID']);
|
||||
|
||||
if (!$data) {
|
||||
$vf->output(['success' => false, 'errors' => 'No data returned from VirtFusion.'], true, true, 502);
|
||||
|
||||
}
|
||||
|
||||
$vf->updateWhmcsServiceParamsOnServerObject((int)$_GET['serviceID'], $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
|
||||
$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;
|
||||
|
||||
/**
|
||||
*
|
||||
* Impersonate server owner.
|
||||
*
|
||||
*/
|
||||
case 'impersonateServerOwner':
|
||||
|
||||
if ($vf->validateServiceID(true)) {
|
||||
|
||||
$service = Database::getSystemService((int)$_GET['serviceID']);
|
||||
|
||||
if (!$service) {
|
||||
$vf->output(['success' => false, 'errors' => 'Service not found'], true, true, 404);
|
||||
}
|
||||
|
||||
$whmcsService = Database::getWhmcsService((int)$_GET['serviceID']);
|
||||
|
||||
if (!$whmcsService) {
|
||||
$vf->output(['success' => false, 'errors' => 'WHMCS service not found'], true, true, 404);
|
||||
}
|
||||
|
||||
$cp = $vf->getCP($whmcsService->server);
|
||||
|
||||
if (!$cp) {
|
||||
$vf->output(['success' => false, 'errors' => 'Control server not found'], true, true, 500);
|
||||
}
|
||||
|
||||
$request = $vf->initCurl($cp['token']);
|
||||
|
||||
$data = $request->get($cp['url'] . '/users/' . $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);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Received HTTP code ' . $request->getRequestInfo('http_code')], true, true, 502);
|
||||
$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:
|
||||
/** No valid action was specified **/
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ EOT;
|
||||
|
||||
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>
|
||||
@@ -34,6 +35,7 @@ EOT;
|
||||
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">
|
||||
|
||||
@@ -170,27 +170,4 @@ class Cache
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache keys matching a pattern.
|
||||
*
|
||||
* @param string $pattern Glob pattern (e.g., "os:*")
|
||||
*/
|
||||
public static function forgetPattern($pattern)
|
||||
{
|
||||
$redis = self::redis();
|
||||
if ($redis) {
|
||||
try {
|
||||
$keys = $redis->keys(self::PREFIX . $pattern);
|
||||
if (!empty($keys)) {
|
||||
$redis->del($keys);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Continue to file cleanup
|
||||
}
|
||||
}
|
||||
|
||||
// File cache: can only clear all files for pattern matches
|
||||
// Since file names are md5 hashed, we can't match patterns.
|
||||
// For non-Redis, TTL expiry handles cleanup naturally.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,31 +379,6 @@ class Module
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a backup plan to a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @param int $planId Backup plan ID (0 to remove)
|
||||
* @return object|false
|
||||
*/
|
||||
public function assignBackupPlan($serviceID, $planId)
|
||||
{
|
||||
$planId = (int) $planId;
|
||||
$ctx = $this->resolveServiceContext($serviceID);
|
||||
if (!$ctx) return false;
|
||||
|
||||
$ctx['request']->addOption(CURLOPT_POSTFIELDS, json_encode(['planId' => $planId]));
|
||||
$endpoint = $ctx['cp']['url'] . '/servers/' . $ctx['serverId'] . '/backup/plan';
|
||||
$data = $planId > 0 ? $ctx['request']->post($endpoint) : $ctx['request']->delete($endpoint);
|
||||
Log::insert(__FUNCTION__, $ctx['request']->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $ctx['request']->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// VNC Console
|
||||
// =========================================================================
|
||||
@@ -740,40 +715,6 @@ class Module
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available self-service currencies.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return array|false
|
||||
*/
|
||||
public function getSelfServiceCurrencies($serviceID)
|
||||
{
|
||||
$cacheKey = 'ss_currencies';
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$serviceID = (int) $serviceID;
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
if (!$whmcsService) return false;
|
||||
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
if (!$cp) return false;
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/selfService/currencies');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
if ($request->getRequestInfo('http_code') == 200) {
|
||||
$result = json_decode($data, true);
|
||||
Cache::set($cacheKey, $result, 1800);
|
||||
return $result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a response from JSON into an associative array.
|
||||
*
|
||||
|
||||
49
modules/servers/VirtFusionDirect/modify.sql
Normal file
49
modules/servers/VirtFusionDirect/modify.sql
Normal file
@@ -0,0 +1,49 @@
|
||||
-- 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);
|
||||
@@ -663,31 +663,6 @@ function vfLoadSelfServiceUsage(serviceId, systemUrl) {
|
||||
});
|
||||
}
|
||||
|
||||
function vfLoadSelfServiceReport(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: vfUrl(systemUrl, serviceId, "selfServiceReport")
|
||||
}).done(function (response) {
|
||||
if (response.success && response.data) {
|
||||
var data = response.data.data || response.data;
|
||||
var tbody = $("#vf-ss-usage-table");
|
||||
tbody.empty();
|
||||
|
||||
var items = data.items || data.report || [];
|
||||
if (Array.isArray(items) && items.length > 0) {
|
||||
$.each(items, function (i, item) {
|
||||
var desc = item.description || item.name || "Item";
|
||||
var cost = item.cost !== undefined ? parseFloat(item.cost).toFixed(2) : "-";
|
||||
tbody.append('<tr><td>' + $('<span>').text(desc).html() + '</td><td class="text-right">' + $('<span>').text(cost).html() + '</td></tr>');
|
||||
});
|
||||
} else {
|
||||
tbody.append('<tr><td colspan="2" class="text-muted">No report data available</td></tr>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function vfAddCredit(serviceId, systemUrl) {
|
||||
var amount = $("#vf-ss-credit-amount").val();
|
||||
var alertDiv = $("#vf-selfservice-alert");
|
||||
|
||||
Reference in New Issue
Block a user