596 lines
22 KiB
PHP
596 lines
22 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use WHMCS\Database\Capsule;
|
|
|
|
if (!defined('WHMCS')) {
|
|
die('This file cannot be accessed directly');
|
|
}
|
|
|
|
require_once __DIR__ . '/lib/ApiClient.php';
|
|
require_once __DIR__ . '/lib/ModuleStorage.php';
|
|
require_once __DIR__ . '/lib/EventPoller.php';
|
|
require_once __DIR__ . '/lib/ProductMappingStorage.php';
|
|
require_once __DIR__ . '/../../../lib/MtlsConfig.php';
|
|
|
|
const HEXAGAMECLOUD_ADDON_VERSION = '1.1.0';
|
|
|
|
function hexagamecloud_config(): array
|
|
{
|
|
return [
|
|
'name' => 'HexaHost GameCloud',
|
|
'description' => 'Global GameCloud integration — dashboard, reconciliation, and event polling.',
|
|
'version' => HEXAGAMECLOUD_ADDON_VERSION,
|
|
'author' => 'HexaHost',
|
|
'language' => 'english',
|
|
'fields' => [
|
|
'api_url' => [
|
|
'FriendlyName' => 'GameCloud API URL',
|
|
'Type' => 'text',
|
|
'Size' => '128',
|
|
'Default' => 'https://api.example.net',
|
|
'Description' => 'Base URL of the GameCloud API (no trailing slash)',
|
|
],
|
|
'integration_id' => [
|
|
'FriendlyName' => 'Integration ID',
|
|
'Type' => 'text',
|
|
'Size' => '64',
|
|
'Description' => 'Matches WHMCS_INTEGRATION_ID on GameCloud',
|
|
],
|
|
'api_secret' => [
|
|
'FriendlyName' => 'API Secret',
|
|
'Type' => 'password',
|
|
'Size' => '128',
|
|
'Description' => 'HMAC signing secret — stored encrypted by WHMCS',
|
|
],
|
|
'sso_origin' => [
|
|
'FriendlyName' => 'SSO Panel Origin',
|
|
'Type' => 'text',
|
|
'Size' => '128',
|
|
'Default' => 'https://panel.example.net',
|
|
'Description' => 'Customer panel URL for SSO redirects',
|
|
],
|
|
'event_poll_limit' => [
|
|
'FriendlyName' => 'Event poll batch size',
|
|
'Type' => 'text',
|
|
'Size' => '4',
|
|
'Default' => '50',
|
|
],
|
|
'auto_reconcile' => [
|
|
'FriendlyName' => 'Auto-reconcile on daily cron',
|
|
'Type' => 'yesno',
|
|
'Description' => 'Run dry reconciliation during daily event poll',
|
|
],
|
|
'use_mtls' => [
|
|
'FriendlyName' => 'Use mTLS client certificate',
|
|
'Type' => 'yesno',
|
|
'Description' => 'Requires INTEGRATION_MTLS_ENABLED=true on GameCloud',
|
|
],
|
|
'mtls_cert_path' => [
|
|
'FriendlyName' => 'mTLS client certificate path',
|
|
'Type' => 'text',
|
|
'Size' => '256',
|
|
],
|
|
'mtls_key_path' => [
|
|
'FriendlyName' => 'mTLS client private key path',
|
|
'Type' => 'text',
|
|
'Size' => '256',
|
|
],
|
|
'mtls_ca_path' => [
|
|
'FriendlyName' => 'mTLS CA bundle path',
|
|
'Type' => 'text',
|
|
'Size' => '256',
|
|
],
|
|
'mtls_cert_fingerprint' => [
|
|
'FriendlyName' => 'Client certificate SHA-256 fingerprint',
|
|
'Type' => 'text',
|
|
'Size' => '128',
|
|
'Description' => 'openssl x509 -in client.crt -outform DER | openssl dgst -sha256',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
function hexagamecloud_activate(): array
|
|
{
|
|
try {
|
|
$sql = file_get_contents(__DIR__ . '/sql/install.sql');
|
|
if ($sql === false) {
|
|
throw new RuntimeException('Unable to read install.sql');
|
|
}
|
|
|
|
foreach (array_filter(array_map('trim', explode(';', $sql))) as $statement) {
|
|
if ($statement !== '') {
|
|
Capsule::connection()->statement($statement);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'description' => 'HexaHost GameCloud addon activated. Copy hooks/hexagamecloud.php to includes/hooks/ for cron polling.',
|
|
];
|
|
} catch (Throwable $exception) {
|
|
return [
|
|
'status' => 'error',
|
|
'description' => 'Activation failed: ' . $exception->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
function hexagamecloud_deactivate(): array
|
|
{
|
|
return [
|
|
'status' => 'success',
|
|
'description' => 'Addon deactivated. Module tables were kept for audit history.',
|
|
];
|
|
}
|
|
|
|
function hexagamecloud_upgrade(array $vars): void
|
|
{
|
|
if (!function_exists('hexagamecloud_getModuleVersion')) {
|
|
return;
|
|
}
|
|
|
|
$current = hexagamecloud_getModuleVersion();
|
|
if (version_compare($current, '1.1.0', '<')) {
|
|
$upgrade = file_get_contents(__DIR__ . '/sql/upgrade-1.1.0.sql');
|
|
if ($upgrade !== false) {
|
|
foreach (array_filter(array_map('trim', explode(';', $upgrade))) as $statement) {
|
|
if ($statement !== '') {
|
|
Capsule::connection()->statement($statement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function hexagamecloud_output(array $vars): void
|
|
{
|
|
$action = $_GET['action'] ?? 'dashboard';
|
|
$modulelink = $vars['modulelink'];
|
|
$storage = new HexaGameCloudModuleStorage();
|
|
|
|
try {
|
|
$client = hexagamecloud_create_client($vars);
|
|
} catch (Throwable $exception) {
|
|
hexagamecloud_render_error($vars, $exception->getMessage());
|
|
return;
|
|
}
|
|
|
|
switch ($action) {
|
|
case 'events':
|
|
hexagamecloud_page_events($vars, $client, $storage, $modulelink);
|
|
break;
|
|
case 'reconciliation':
|
|
hexagamecloud_page_reconciliation($vars, $client, $storage, $modulelink);
|
|
break;
|
|
case 'poll':
|
|
hexagamecloud_action_poll($vars, $client, $storage, $modulelink);
|
|
break;
|
|
case 'reconcile':
|
|
hexagamecloud_action_reconcile($vars, $client, $storage, $modulelink);
|
|
break;
|
|
case 'ack':
|
|
hexagamecloud_action_ack($vars, $client, $storage, $modulelink);
|
|
break;
|
|
case 'mappings':
|
|
hexagamecloud_page_mappings($vars, $client, $modulelink);
|
|
break;
|
|
case 'save_mapping':
|
|
hexagamecloud_action_save_mapping($vars, $client, $modulelink);
|
|
break;
|
|
case 'delete_mapping':
|
|
hexagamecloud_action_delete_mapping($vars, $modulelink);
|
|
break;
|
|
case 'mtls':
|
|
hexagamecloud_page_mtls($vars, $client, $modulelink);
|
|
break;
|
|
case 'register_mtls':
|
|
hexagamecloud_action_register_mtls($vars, $client, $modulelink);
|
|
break;
|
|
default:
|
|
hexagamecloud_page_dashboard($vars, $client, $storage, $modulelink);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function hexagamecloud_create_client(array $vars): HexaGameCloudAddonApiClient
|
|
{
|
|
$apiUrl = trim((string) ($vars['api_url'] ?? ''));
|
|
$integrationId = trim((string) ($vars['integration_id'] ?? ''));
|
|
$apiSecret = trim((string) ($vars['api_secret'] ?? ''));
|
|
|
|
if ($apiUrl === '' || $integrationId === '' || $apiSecret === '') {
|
|
throw new RuntimeException('Configure API URL, Integration ID and API Secret in addon settings.');
|
|
}
|
|
|
|
$mtls = HexaGameCloudMtlsConfig::fromAddonSettings($vars);
|
|
|
|
return new HexaGameCloudAddonApiClient($apiUrl, $integrationId, $apiSecret, $mtls);
|
|
}
|
|
|
|
function hexagamecloud_page_dashboard(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
$health = $client->health();
|
|
$stats = $client->dashboardStats();
|
|
$accounts = $client->listAccounts();
|
|
$unlinked = hexagamecloud_count_unlinked_whmcs_services($accounts['accounts'] ?? []);
|
|
|
|
$orphans = 0;
|
|
foreach ($accounts['accounts'] ?? [] as $account) {
|
|
if (($account['linkStatus'] ?? '') === 'TERMINATED' && ($account['gameCloudStatus'] ?? '') !== 'DELETED') {
|
|
$orphans++;
|
|
}
|
|
}
|
|
|
|
$storage->recordOperation('dashboard:view', 'success', [
|
|
'integrationId' => $stats['integrationId'] ?? null,
|
|
]);
|
|
|
|
echo hexagamecloud_render_template('dashboard', [
|
|
'modulelink' => $modulelink,
|
|
'version' => HEXAGAMECLOUD_ADDON_VERSION,
|
|
'health' => $health,
|
|
'stats' => $stats,
|
|
'linkedServices' => count($accounts['accounts'] ?? []),
|
|
'unlinkedWhmcsServices' => $unlinked,
|
|
'driftCount' => $orphans,
|
|
'lastError' => $storage->getLastError(),
|
|
]);
|
|
}
|
|
|
|
function hexagamecloud_page_events(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
$localEvents = $storage->listLocalEvents(100);
|
|
$pendingCount = $storage->countLocalEventsByStatus('pending');
|
|
|
|
echo hexagamecloud_render_template('events', [
|
|
'modulelink' => $modulelink,
|
|
'localEvents' => $localEvents,
|
|
'pendingCount' => $pendingCount,
|
|
'deadLetterCount' => $storage->countLocalEventsByStatus('dead_letter'),
|
|
]);
|
|
}
|
|
|
|
function hexagamecloud_page_reconciliation(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
$runs = $storage->listReconciliationRuns(20);
|
|
$dryRun = isset($_GET['dry']) ? true : false;
|
|
$liveResult = null;
|
|
|
|
if (isset($_GET['run'])) {
|
|
try {
|
|
$liveResult = $client->reconcileAll([
|
|
'dryRun' => $dryRun,
|
|
'autoRepair' => !$dryRun && !empty($_GET['repair']),
|
|
]);
|
|
$storage->saveReconciliationRun($liveResult, $dryRun ? 'dry_run' : 'live');
|
|
} catch (Throwable $exception) {
|
|
$storage->setLastError($exception->getMessage());
|
|
$liveResult = ['error' => $exception->getMessage()];
|
|
}
|
|
}
|
|
|
|
echo hexagamecloud_render_template('reconciliation', [
|
|
'modulelink' => $modulelink,
|
|
'runs' => $runs,
|
|
'liveResult' => $liveResult,
|
|
'dryRun' => $dryRun,
|
|
]);
|
|
}
|
|
|
|
function hexagamecloud_action_poll(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
$limit = (int) ($vars['event_poll_limit'] ?? 50);
|
|
$poller = new HexaGameCloudEventPoller($client, $storage, max(1, min($limit, 100)));
|
|
|
|
try {
|
|
$result = $poller->pollAndAcknowledge();
|
|
$storage->recordOperation('events:poll', 'success', $result);
|
|
} catch (Throwable $exception) {
|
|
$storage->setLastError($exception->getMessage());
|
|
$storage->recordOperation('events:poll', 'error', ['message' => $exception->getMessage()]);
|
|
}
|
|
|
|
header('Location: ' . $modulelink . '&action=events');
|
|
exit;
|
|
}
|
|
|
|
function hexagamecloud_action_reconcile(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
header('Location: ' . $modulelink . '&action=reconciliation&run=1&dry=1');
|
|
exit;
|
|
}
|
|
|
|
function hexagamecloud_action_ack(
|
|
array $vars,
|
|
HexaGameCloudAddonApiClient $client,
|
|
HexaGameCloudModuleStorage $storage,
|
|
string $modulelink
|
|
): void {
|
|
$eventId = (int) ($_POST['local_event_id'] ?? 0);
|
|
$deadLetter = !empty($_POST['dead_letter']);
|
|
$local = $storage->getLocalEvent($eventId);
|
|
|
|
if ($local && !empty($local['remote_event_id'])) {
|
|
try {
|
|
$client->acknowledgeEvents([$local['remote_event_id']], $deadLetter, 'Manual ack from WHMCS addon');
|
|
$storage->updateLocalEventStatus($eventId, $deadLetter ? 'dead_letter' : 'acknowledged');
|
|
} catch (Throwable $exception) {
|
|
$storage->setLastError($exception->getMessage());
|
|
}
|
|
}
|
|
|
|
header('Location: ' . $modulelink . '&action=events');
|
|
exit;
|
|
}
|
|
|
|
function hexagamecloud_count_unlinked_whmcs_services(array $linkedAccounts): int
|
|
{
|
|
$linkedIds = [];
|
|
foreach ($linkedAccounts as $account) {
|
|
if (!empty($account['externalServiceId'])) {
|
|
$linkedIds[(string) $account['externalServiceId']] = true;
|
|
}
|
|
}
|
|
|
|
$hosting = Capsule::table('tblhosting')
|
|
->join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid')
|
|
->where('tblproducts.servertype', 'hexagamecloud')
|
|
->whereIn('tblhosting.domainstatus', ['Active', 'Suspended', 'Pending'])
|
|
->select('tblhosting.id')
|
|
->get();
|
|
|
|
$unlinked = 0;
|
|
foreach ($hosting as $row) {
|
|
if (!isset($linkedIds[(string) $row->id])) {
|
|
$unlinked++;
|
|
}
|
|
}
|
|
|
|
return $unlinked;
|
|
}
|
|
|
|
function hexagamecloud_render_template(string $name, array $data): string
|
|
{
|
|
$path = __DIR__ . '/templates/' . $name . '.tpl';
|
|
if (!is_readable($path)) {
|
|
return '<div class="alert alert-danger">Template missing: ' . htmlspecialchars($name) . '</div>';
|
|
}
|
|
|
|
$html = file_get_contents($path);
|
|
if ($html === false) {
|
|
return '';
|
|
}
|
|
|
|
foreach ($data as $key => $value) {
|
|
$placeholder = '{{' . $key . '}}';
|
|
if (is_scalar($value) || $value === null) {
|
|
$html = str_replace($placeholder, htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
|
|
}
|
|
}
|
|
|
|
return hexagamecloud_expand_blocks($html, $data);
|
|
}
|
|
|
|
function hexagamecloud_expand_blocks(string $html, array $data): string
|
|
{
|
|
if (!empty($data['stats']) && is_array($data['stats'])) {
|
|
foreach ($data['stats'] as $key => $value) {
|
|
$html = str_replace('{{stats.' . $key . '}}', htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
|
|
}
|
|
}
|
|
|
|
if (!empty($data['health']) && is_array($data['health'])) {
|
|
foreach ($data['health'] as $key => $value) {
|
|
$html = str_replace('{{health.' . $key . '}}', htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
|
|
}
|
|
}
|
|
|
|
if (!empty($data['localEvents']) && is_array($data['localEvents'])) {
|
|
$rows = '';
|
|
foreach ($data['localEvents'] as $event) {
|
|
$rows .= '<tr>';
|
|
$rows .= '<td>' . (int) $event['id'] . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $event['event_type'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $event['status'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td><code>' . htmlspecialchars((string) $event['payload'], ENT_QUOTES, 'UTF-8') . '</code></td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $event['created_at'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td><form method="post" action="' . htmlspecialchars((string) $data['modulelink'], ENT_QUOTES, 'UTF-8') . '&action=ack">';
|
|
$rows .= '<input type="hidden" name="local_event_id" value="' . (int) $event['id'] . '">';
|
|
$rows .= '<button type="submit" class="btn btn-default btn-xs">Ack</button> ';
|
|
$rows .= '<button type="submit" name="dead_letter" value="1" class="btn btn-danger btn-xs">Dead letter</button>';
|
|
$rows .= '</form></td>';
|
|
$rows .= '</tr>';
|
|
}
|
|
$html = str_replace('{{events.rows}}', $rows, $html);
|
|
}
|
|
|
|
if (!empty($data['runs']) && is_array($data['runs'])) {
|
|
$rows = '';
|
|
foreach ($data['runs'] as $run) {
|
|
$rows .= '<tr>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $run['run_id'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $run['mode'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . (int) $run['issue_count'] . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $run['created_at'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '</tr>';
|
|
}
|
|
$html = str_replace('{{reconciliation.rows}}', $rows, $html);
|
|
}
|
|
|
|
if (!empty($data['liveResult']) && is_array($data['liveResult'])) {
|
|
$html = str_replace(
|
|
'{{reconciliation.result}}',
|
|
'<pre>' . htmlspecialchars(json_encode($data['liveResult'], JSON_PRETTY_PRINT), ENT_QUOTES, 'UTF-8') . '</pre>',
|
|
$html
|
|
);
|
|
} else {
|
|
$html = str_replace('{{reconciliation.result}}', '', $html);
|
|
}
|
|
|
|
if (!empty($data['status']) && is_array($data['status'])) {
|
|
foreach ($data['status'] as $key => $value) {
|
|
$display = is_bool($value) ? ($value ? 'yes' : 'no') : (string) $value;
|
|
$html = str_replace('{{status.' . $key . '}}', htmlspecialchars($display, ENT_QUOTES, 'UTF-8'), $html);
|
|
}
|
|
}
|
|
|
|
if (!empty($data['products']) && is_array($data['products'])) {
|
|
$productOptions = '';
|
|
foreach ($data['products'] as $product) {
|
|
$productOptions .= '<option value="' . (int) $product['id'] . '">'
|
|
. htmlspecialchars((string) $product['name'], ENT_QUOTES, 'UTF-8')
|
|
. ' (#' . (int) $product['id'] . ')</option>';
|
|
}
|
|
$html = str_replace('{{mappings.product_options}}', $productOptions, $html);
|
|
}
|
|
|
|
if (!empty($data['plans']['plans']) && is_array($data['plans']['plans'])) {
|
|
$planOptions = '';
|
|
foreach ($data['plans']['plans'] as $plan) {
|
|
$planOptions .= '<option value="' . htmlspecialchars((string) $plan['slug'], ENT_QUOTES, 'UTF-8') . '">'
|
|
. htmlspecialchars((string) $plan['name'], ENT_QUOTES, 'UTF-8')
|
|
. ' (' . htmlspecialchars((string) $plan['slug'], ENT_QUOTES, 'UTF-8') . ')</option>';
|
|
}
|
|
$html = str_replace('{{mappings.plan_options}}', $planOptions, $html);
|
|
}
|
|
|
|
if (!empty($data['mappings']) && is_array($data['mappings'])) {
|
|
$rows = '';
|
|
foreach ($data['mappings'] as $mapping) {
|
|
$rows .= '<tr>';
|
|
$rows .= '<td>' . (int) $mapping['whmcs_product_id'] . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $mapping['whmcs_product_name'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $mapping['plan_slug'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . htmlspecialchars((string) $mapping['default_software'], ENT_QUOTES, 'UTF-8') . '</td>';
|
|
$rows .= '<td>' . ((int) $mapping['is_active'] === 1 ? 'yes' : 'no') . '</td>';
|
|
$rows .= '<td><a class="btn btn-danger btn-xs" href="'
|
|
. htmlspecialchars((string) $data['modulelink'], ENT_QUOTES, 'UTF-8')
|
|
. '&action=delete_mapping&product_id=' . (int) $mapping['whmcs_product_id']
|
|
. '">Delete</a></td>';
|
|
$rows .= '</tr>';
|
|
}
|
|
$html = str_replace('{{mappings.rows}}', $rows, $html);
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
function hexagamecloud_render_error(array $vars, string $message): void
|
|
{
|
|
echo '<div class="alert alert-danger"><strong>GameCloud addon error:</strong> ' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</div>';
|
|
echo '<p>Configure the addon under <em>Setup → Addon Modules → HexaHost GameCloud</em>.</p>';
|
|
}
|
|
|
|
function hexagamecloud_page_mappings(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
|
|
{
|
|
$mappingStorage = new HexaGameCloudProductMappingStorage();
|
|
$plans = $client->listPlans();
|
|
$products = $mappingStorage->listWhmcsProducts();
|
|
$mappings = $mappingStorage->listMappings();
|
|
$validation = null;
|
|
|
|
if (!empty($_GET['validate'])) {
|
|
try {
|
|
$validation = $client->validatePlan((string) $_GET['validate']);
|
|
} catch (Throwable $exception) {
|
|
$validation = ['valid' => false, 'error' => $exception->getMessage()];
|
|
}
|
|
}
|
|
|
|
echo hexagamecloud_render_template('mappings', [
|
|
'modulelink' => $modulelink,
|
|
'plans' => $plans,
|
|
'products' => $products,
|
|
'mappings' => $mappings,
|
|
'validation' => $validation,
|
|
]);
|
|
}
|
|
|
|
function hexagamecloud_action_save_mapping(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
|
|
{
|
|
$mappingStorage = new HexaGameCloudProductMappingStorage();
|
|
$planSlug = trim((string) ($_POST['plan_slug'] ?? ''));
|
|
|
|
try {
|
|
$validation = $client->validatePlan($planSlug);
|
|
if (empty($validation['valid'])) {
|
|
throw new RuntimeException('Invalid GameCloud plan slug: ' . $planSlug);
|
|
}
|
|
|
|
$mappingStorage->upsertMapping([
|
|
'whmcs_product_id' => (int) ($_POST['whmcs_product_id'] ?? 0),
|
|
'whmcs_product_name' => (string) ($_POST['whmcs_product_name'] ?? ''),
|
|
'plan_slug' => $planSlug,
|
|
'default_edition' => (string) ($_POST['default_edition'] ?? 'JAVA'),
|
|
'default_software' => (string) ($_POST['default_software'] ?? 'VANILLA'),
|
|
'default_version' => (string) ($_POST['default_version'] ?? '1.21.1'),
|
|
'is_active' => !empty($_POST['is_active']),
|
|
]);
|
|
} catch (Throwable $exception) {
|
|
echo '<div class="alert alert-danger">' . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
|
|
hexagamecloud_page_mappings($vars, $client, $modulelink);
|
|
return;
|
|
}
|
|
|
|
header('Location: ' . $modulelink . '&action=mappings');
|
|
exit;
|
|
}
|
|
|
|
function hexagamecloud_action_delete_mapping(array $vars, string $modulelink): void
|
|
{
|
|
$mappingStorage = new HexaGameCloudProductMappingStorage();
|
|
$mappingStorage->deleteMapping((int) ($_GET['product_id'] ?? 0));
|
|
header('Location: ' . $modulelink . '&action=mappings');
|
|
exit;
|
|
}
|
|
|
|
function hexagamecloud_page_mtls(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
|
|
{
|
|
$status = $client->mtlsStatus();
|
|
echo hexagamecloud_render_template('mtls', [
|
|
'modulelink' => $modulelink,
|
|
'status' => $status,
|
|
'fingerprint_configured' => trim((string) ($vars['mtls_cert_fingerprint'] ?? '')),
|
|
]);
|
|
}
|
|
|
|
function hexagamecloud_action_register_mtls(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
|
|
{
|
|
$fingerprint = trim((string) ($_POST['fingerprint'] ?? $vars['mtls_cert_fingerprint'] ?? ''));
|
|
$subject = trim((string) ($_POST['subject'] ?? ''));
|
|
|
|
try {
|
|
$client->registerMtlsFingerprint($fingerprint, $subject !== '' ? $subject : null);
|
|
} catch (Throwable $exception) {
|
|
echo '<div class="alert alert-danger">' . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
|
|
hexagamecloud_page_mtls($vars, $client, $modulelink);
|
|
return;
|
|
}
|
|
|
|
header('Location: ' . $modulelink . '&action=mtls');
|
|
exit;
|
|
}
|