- Remove client IP removal capability (keep backend methods removed too) - Add copy-to-clipboard buttons for IP addresses with tooltip feedback - Replace OS dropdown with tile gallery (grouped, searchable, brand colors, EOL badges) in rebuild panel and checkout page - Add inline server rename with friendly name generator and RFC 1123 validation - Add traffic statistics canvas chart with responsive resize in resources panel - Add backup listing timeline in manage panel with show-all expansion - Add VNC enable/disable toggle with connection details and password copy - Add server root password reset with auto-clipboard copy (never displayed) - Add skeleton loading placeholders, action cooldowns (power 3s, rebuild 30s), progress indicator with elapsed timer - Sanitize all client-facing error messages (no raw API errors exposed) - Convert all state-mutating AJAX calls from GET to POST - Add explicit break after all output() calls in client.php - Add Redis-backed API response caching (Cache.php): OS templates 10min, traffic/backups 2min, currencies 30min, packages 10min - Add GitHub Actions workflow for weekly VirtFusion API change detection - Move cache busting step after semantic-release in publish workflow - Add endpoint doc generator script and OpenAPI baseline placeholder - Improve hostname generation entropy (bin2hex random_bytes) - Add .superpowers/ to .gitignore Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
957 lines
30 KiB
PHP
957 lines
30 KiB
PHP
<?php
|
|
|
|
namespace WHMCS\Module\Server\VirtFusionDirect;
|
|
|
|
class Module
|
|
{
|
|
public function __construct()
|
|
{
|
|
Database::schema();
|
|
}
|
|
|
|
/**
|
|
* @param bool $exitOnError
|
|
* @return string
|
|
*/
|
|
public function validateAction($exitOnError = true)
|
|
{
|
|
if (!isset($_GET['action'])) {
|
|
$this->output(['success' => false, 'errors' => 'no action specified'], true, $exitOnError, 400);
|
|
}
|
|
return preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['action']);
|
|
}
|
|
|
|
/**
|
|
* @param bool $exitOnError
|
|
* @return int
|
|
*/
|
|
public function validateServiceID($exitOnError = true)
|
|
{
|
|
if (!isset($_GET['serviceID']) || !is_numeric($_GET['serviceID'])) {
|
|
$this->output(['success' => false, 'errors' => 'no valid serviceID specified'], true, $exitOnError, 400);
|
|
}
|
|
return (int) $_GET['serviceID'];
|
|
}
|
|
|
|
/**
|
|
* @param int $serviceID
|
|
* @param bool $exitOnError
|
|
* @return int|false
|
|
*/
|
|
public function validateUserOwnsService($serviceID, $exitOnError = true)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$currentUser = new \WHMCS\Authentication\CurrentUser;
|
|
$client = $currentUser->client();
|
|
|
|
if (!$client) {
|
|
return false;
|
|
}
|
|
|
|
if (Database::userWhmcsService($serviceID, $client->id)) {
|
|
return $client->id;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param int $serviceID
|
|
* @return false|string
|
|
*/
|
|
public function fetchLoginTokens($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->post($cp['url'] . '/users/' . (int) $whmcsService->userid . '/serverAuthenticationTokens/' . (int) $service->server_id);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == '200') {
|
|
$data = json_decode($data);
|
|
if (isset($data->data->authentication->endpoint_complete)) {
|
|
return $cp['base_url'] . $data->data->authentication->endpoint_complete;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function updateWhmcsServiceParamsOnServerObject($serviceId, $data)
|
|
{
|
|
$output = [];
|
|
|
|
$serverResource = (new ServerResource())->process($data);
|
|
|
|
$dedicatedIpv4 = null;
|
|
|
|
if (count($serverResource['primaryNetwork']['ipv4Unformatted'])) {
|
|
$dedicatedIpv4 = $serverResource['primaryNetwork']['ipv4Unformatted'][0];
|
|
}
|
|
|
|
if ($serverResource['hostname'] == '-') {
|
|
if ($serverResource['name'] == '-') {
|
|
$name = '';
|
|
} else {
|
|
$name = $serverResource['name'];
|
|
}
|
|
} else {
|
|
$name = $serverResource['hostname'];
|
|
}
|
|
|
|
$output['tblhosting'] = ["dedicatedip" => $dedicatedIpv4, "domain" => $name, "username" => $serverResource['username'], "password" => $serverResource['password']];
|
|
|
|
Database::updateWhmcsServiceParams($serviceId, $output);
|
|
}
|
|
|
|
public function updateWhmcsServiceParamsOnDestroy($serviceId)
|
|
{
|
|
$output['tblhosting'] = ["dedicatedip" => null];
|
|
|
|
Database::updateWhmcsServiceParams($serviceId, $output);
|
|
}
|
|
|
|
public function fetchServerData($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$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'] . '/servers/' . (int) $service->server_id);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == '200') {
|
|
return json_decode($data);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Execute a power action on a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @param string $action One of: boot, shutdown, restart, poweroff
|
|
* @return object|false
|
|
*/
|
|
public function serverPowerAction($serviceID, $action)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$allowedActions = ['boot', 'shutdown', 'restart', 'poweroff'];
|
|
if (!in_array($action, $allowedActions, true)) {
|
|
return false;
|
|
}
|
|
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/power/' . $action);
|
|
|
|
Log::insert(__FUNCTION__ . ':' . $action, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 204) {
|
|
return json_decode($data) ?: (object) ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Rebuild/reinstall a server with a new OS.
|
|
*
|
|
* @param int $serviceID
|
|
* @param int $osId Operating system template ID
|
|
* @param string|null $hostname Optional new hostname
|
|
* @return object|false
|
|
*/
|
|
public function rebuildServer($serviceID, $osId, $hostname = null)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$osId = (int) $osId;
|
|
|
|
if ($osId <= 0) {
|
|
return false;
|
|
}
|
|
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
|
|
$buildData = [
|
|
'operatingSystemId' => $osId,
|
|
'email' => true,
|
|
];
|
|
|
|
if ($hostname !== null && $hostname !== '') {
|
|
$buildData['hostname'] = $hostname;
|
|
}
|
|
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode($buildData));
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/build');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 201) {
|
|
Cache::forgetPattern('backups:' . (int) $service->server_id);
|
|
return json_decode($data) ?: (object) ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Rename a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @param string $newName
|
|
* @return bool
|
|
*/
|
|
public function renameServer($serviceID, $newName)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$newName = trim($newName);
|
|
|
|
if (empty($newName) || strlen($newName) > 255) {
|
|
return false;
|
|
}
|
|
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['name' => $newName]));
|
|
$data = $request->patch($cp['url'] . '/servers/' . (int) $service->server_id . '/name');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
return ($httpCode == 200 || $httpCode == 204);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fetch available OS templates for a server's package.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function fetchOsTemplates($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$product = \WHMCS\Database\Capsule::table('tblproducts')->where('id', $whmcsService->packageid)->first();
|
|
if (!$product || !$product->configoption2) {
|
|
return false;
|
|
}
|
|
|
|
$cacheKey = 'os:' . (int) $product->configoption2;
|
|
$cached = Cache::get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->get($cp['url'] . '/media/templates/fromServerPackageSpec/' . (int) $product->configoption2);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == '200') {
|
|
$templates = json_decode($data, true);
|
|
$baseUrl = rtrim(str_replace('/api/v1', '', $cp['url']), '/');
|
|
$categories = [];
|
|
$otherTemplates = [];
|
|
|
|
if (isset($templates['data'])) {
|
|
foreach ($templates['data'] as $osCategory) {
|
|
$catTemplates = [];
|
|
foreach ($osCategory['templates'] as $template) {
|
|
$catTemplates[] = [
|
|
'id' => $template['id'],
|
|
'name' => $template['name'],
|
|
'version' => $template['version'] ?? '',
|
|
'variant' => $template['variant'] ?? '',
|
|
'icon' => $template['icon'] ?? null,
|
|
'eol' => $template['eol'] ?? false,
|
|
'type' => $template['type'] ?? '',
|
|
'description' => $template['description'] ?? '',
|
|
];
|
|
}
|
|
|
|
if (count($catTemplates) <= 1) {
|
|
$otherTemplates = array_merge($otherTemplates, $catTemplates);
|
|
} else {
|
|
$categories[] = [
|
|
'name' => $osCategory['name'] ?? 'Unknown',
|
|
'icon' => $osCategory['icon'] ?? null,
|
|
'templates' => $catTemplates,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!empty($otherTemplates)) {
|
|
$categories[] = [
|
|
'name' => 'Other',
|
|
'icon' => null,
|
|
'templates' => $otherTemplates,
|
|
];
|
|
}
|
|
}
|
|
|
|
$result = [
|
|
'baseUrl' => $baseUrl,
|
|
'categories' => $categories,
|
|
];
|
|
|
|
Cache::set($cacheKey, $result, 600);
|
|
return $result;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Traffic Statistics
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get traffic statistics for a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function getTrafficStats($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$cacheKey = 'traffic:' . (int) $service->server_id;
|
|
$cached = Cache::get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
$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'] . '/servers/' . (int) $service->server_id . '/traffic');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == 200) {
|
|
$result = json_decode($data, true);
|
|
Cache::set($cacheKey, $result, 120);
|
|
return $result;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// IP Address Management
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Add an IPv4 address to a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @return object|false
|
|
*/
|
|
public function addIPv4($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/ipv4');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 201) {
|
|
return json_decode($data) ?: (object) ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Backup Management
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get backup list for a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function getServerBackups($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$cacheKey = 'backups:' . (int) $service->server_id;
|
|
$cached = Cache::get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
$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'] . '/backups/server/' . (int) $service->server_id);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == 200) {
|
|
$result = json_decode($data, true);
|
|
Cache::set($cacheKey, $result, 120);
|
|
return $result;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$planId = (int) $planId;
|
|
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['planId' => $planId]));
|
|
|
|
if ($planId > 0) {
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/backup/plan');
|
|
} else {
|
|
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id . '/backup/plan');
|
|
}
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 204) {
|
|
return json_decode($data) ?: (object) ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// VNC Console
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get VNC console connection details for a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function getVncConsole($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$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'] . '/servers/' . (int) $service->server_id . '/vnc');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == 200) {
|
|
return json_decode($data, true);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Toggle VNC on/off for a server.
|
|
*
|
|
* @param int $serviceID
|
|
* @param bool $enabled
|
|
* @return array|false
|
|
*/
|
|
public function toggleVnc($serviceID, $enabled)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['enabled' => (bool) $enabled]));
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/vnc');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 204) {
|
|
return json_decode($data, true) ?: ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Resource Modification
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Modify a server resource (memory, cpuCores, or traffic).
|
|
*
|
|
* @param int $serviceID
|
|
* @param string $resource One of: memory, cpuCores, traffic
|
|
* @param int $value New value for the resource
|
|
* @return object|false
|
|
*/
|
|
public function modifyResource($serviceID, $resource, $value)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$allowedResources = ['memory', 'cpuCores', 'traffic'];
|
|
if (!in_array($resource, $allowedResources, true)) {
|
|
return false;
|
|
}
|
|
|
|
$value = (int) $value;
|
|
if ($value < 0) {
|
|
return false;
|
|
}
|
|
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode([$resource => $value]));
|
|
$data = $request->put($cp['url'] . '/servers/' . (int) $service->server_id . '/modify/' . $resource);
|
|
|
|
Log::insert(__FUNCTION__ . ':' . $resource, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 204) {
|
|
return json_decode($data) ?: (object) ['success' => true];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Dry Run Validation
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Validate server creation parameters without actually creating a server.
|
|
*
|
|
* @param array $options Server creation options
|
|
* @param int $serverId WHMCS server ID for API credentials
|
|
* @return array ['valid' => bool, 'errors' => array]
|
|
*/
|
|
public function validateServerCreation($options, $serverId)
|
|
{
|
|
$cp = $this->getCP($serverId, !$serverId);
|
|
if (!$cp) {
|
|
return ['valid' => false, 'errors' => ['No control server found']];
|
|
}
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode($options));
|
|
$data = $request->post($cp['url'] . '/servers?dryRun=true');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
$response = json_decode($data, true);
|
|
|
|
if ($httpCode == 200 || $httpCode == 201) {
|
|
return ['valid' => true, 'errors' => []];
|
|
}
|
|
|
|
$errors = [];
|
|
if (isset($response['errors']) && is_array($response['errors'])) {
|
|
$errors = $response['errors'];
|
|
} elseif (isset($response['msg'])) {
|
|
$errors = [$response['msg']];
|
|
} else {
|
|
$errors = ['Validation failed with HTTP ' . $httpCode];
|
|
}
|
|
|
|
return ['valid' => false, 'errors' => $errors];
|
|
}
|
|
|
|
/**
|
|
* Reset the server's root password.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function resetServerPassword($serviceID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/resetPassword');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 201) {
|
|
return json_decode($data, true);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function resetUserPassword($serviceID, $clientID)
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$clientID = (int) $clientID;
|
|
$service = Database::getSystemService($serviceID);
|
|
|
|
if ($service) {
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$data = $request->post($cp['url'] . '/users/' . $clientID . '/byExtRelation/resetPassword');
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == '201') {
|
|
return json_decode($data);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $data
|
|
* @param bool $json
|
|
* @param bool $exit
|
|
* @param int $rspCode
|
|
*/
|
|
public function output($data, $json = true, $exit = true, $rspCode = 200)
|
|
{
|
|
http_response_code($rspCode);
|
|
|
|
if ($json) {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode($data);
|
|
} else {
|
|
echo $data;
|
|
}
|
|
|
|
if ($exit) {
|
|
exit();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $server
|
|
* @return array|false
|
|
*/
|
|
public function getCP($server, $any = false)
|
|
{
|
|
$cp = Database::getWhmcsServer($server, $any);
|
|
|
|
if ($cp) {
|
|
return [
|
|
'url' => 'https://' . $cp->hostname . '/api/v1',
|
|
'base_url' => 'https://' . $cp->hostname,
|
|
'token' => decrypt($cp->password)];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return bool|void
|
|
*/
|
|
public function adminOnly()
|
|
{
|
|
if ((new \WHMCS\Authentication\CurrentUser)->isAuthenticatedAdmin()) {
|
|
return true;
|
|
}
|
|
|
|
$this->output(['success' => false, 'errors' => 'unauthenticated'], true, true, 401);
|
|
}
|
|
|
|
/**
|
|
* @return bool|void
|
|
*/
|
|
public function isAuthenticated()
|
|
{
|
|
if ((new \WHMCS\Authentication\CurrentUser)->isAuthenticatedUser()) {
|
|
return true;
|
|
}
|
|
|
|
$this->output(['success' => false, 'errors' => 'unauthenticated'], true, true, 401);
|
|
}
|
|
|
|
/**
|
|
* @param $token
|
|
* @return \WHMCS\Module\Server\VirtFusionDirect\Curl
|
|
*/
|
|
public function initCurl($token)
|
|
{
|
|
$curl = new Curl();
|
|
|
|
$curl->addOption(CURLOPT_HTTPHEADER, [
|
|
'Accept: application/json',
|
|
'Content-type: application/json; charset=utf-8',
|
|
'authorization: Bearer ' . $token
|
|
]);
|
|
|
|
return $curl;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Self Service — Credit & Usage
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get self-service usage data for a WHMCS client.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function getSelfServiceUsage($serviceID)
|
|
{
|
|
$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/usage/byUserExtRelationId/' . (int) $whmcsService->userid);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == 200) {
|
|
return json_decode($data, true);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get self-service billing report for a WHMCS client.
|
|
*
|
|
* @param int $serviceID
|
|
* @return array|false
|
|
*/
|
|
public function getSelfServiceReport($serviceID)
|
|
{
|
|
$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/report/byUserExtRelationId/' . (int) $whmcsService->userid);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
if ($request->getRequestInfo('http_code') == 200) {
|
|
return json_decode($data, true);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add self-service credit for a WHMCS client.
|
|
*
|
|
* @param int $serviceID
|
|
* @param float $tokens Amount of credit tokens to add
|
|
* @param string $reference Reference text for the transaction
|
|
* @return array|false
|
|
*/
|
|
public function addSelfServiceCredit($serviceID, $tokens, $reference = '')
|
|
{
|
|
$serviceID = (int) $serviceID;
|
|
$tokens = (float) $tokens;
|
|
|
|
if ($tokens <= 0) {
|
|
return false;
|
|
}
|
|
|
|
$whmcsService = Database::getWhmcsService($serviceID);
|
|
if (!$whmcsService) return false;
|
|
|
|
$cp = $this->getCP($whmcsService->server);
|
|
if (!$cp) return false;
|
|
|
|
$request = $this->initCurl($cp['token']);
|
|
$request->addOption(CURLOPT_POSTFIELDS, json_encode([
|
|
'tokens' => $tokens,
|
|
'reference_1' => $reference ?: 'WHMCS Top-up',
|
|
'reference_2' => 'Service #' . $serviceID,
|
|
]));
|
|
$data = $request->post($cp['url'] . '/selfService/credit/byUserExtRelationId/' . (int) $whmcsService->userid);
|
|
|
|
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
|
|
|
$httpCode = $request->getRequestInfo('http_code');
|
|
if ($httpCode == 200 || $httpCode == 201) {
|
|
return json_decode($data, true);
|
|
}
|
|
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.
|
|
*
|
|
* @param string $response
|
|
*
|
|
* @return array
|
|
* @throws \JsonException
|
|
*/
|
|
public function decodeResponseFromJson(string $response): array
|
|
{
|
|
return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
|
|
}
|
|
}
|