fix: auto-provision port/mod assignment, error logging, retry UI, GSP wording
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/98539de7-36c5-4a0e-962e-e30f5e4c9125 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
parent
e0b843897d
commit
8f8a2a4c06
6 changed files with 974 additions and 27 deletions
209
modules/billing/admin_game_defaults.php
Normal file
209
modules/billing/admin_game_defaults.php
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin: Game Mod/Build Defaults for Billing Auto-Provisioning
|
||||
*
|
||||
* Allows admins to:
|
||||
* - See available mods/builds per game config (config_mods table)
|
||||
* - Mark exactly one mod/build per game as the billing auto-install default
|
||||
* (is_default_for_billing = 1)
|
||||
* - Override the per-service mod_cfg_id in billing_services
|
||||
*
|
||||
* A safe migration for is_default_for_billing is included in billing module.php
|
||||
* db_version 8. This page gracefully handles the column being absent.
|
||||
*/
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db, $view, $table_prefix;
|
||||
$db_prefix = isset($table_prefix) ? $table_prefix : '';
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$isAdmin = $db->isAdmin($user_id);
|
||||
|
||||
if (!$isAdmin) {
|
||||
echo "<div class='failure'><p>Access Denied: Admin privileges required.</p></div>";
|
||||
return;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Check whether is_default_for_billing column exists.
|
||||
// It is added by db_version 8 migration; show a warning if missing.
|
||||
// -------------------------------------------------------------------
|
||||
$colExists = false;
|
||||
$colCheck = $db->resultQuery(
|
||||
"SELECT COUNT(*) AS cnt
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '{$db_prefix}config_mods'
|
||||
AND COLUMN_NAME = 'is_default_for_billing'"
|
||||
);
|
||||
if (!empty($colCheck[0]['cnt']) && intval($colCheck[0]['cnt']) > 0) {
|
||||
$colExists = true;
|
||||
}
|
||||
|
||||
if (!$colExists) {
|
||||
echo "<div class='failure'>"
|
||||
. "<p><strong>Database migration required.</strong> "
|
||||
. "The <code>is_default_for_billing</code> column is not present in <code>{$db_prefix}config_mods</code>. "
|
||||
. "Please run the billing module update (Admin → Module Manager → Update) to apply db_version 8.</p>"
|
||||
. "</div>";
|
||||
return;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Handle POST: save default mod for a game (home_cfg_id)
|
||||
// -------------------------------------------------------------------
|
||||
$saveMsg = '';
|
||||
if (isset($_POST['save_default']) && isset($_POST['home_cfg_id'])) {
|
||||
$save_home_cfg_id = intval($_POST['home_cfg_id']);
|
||||
$save_mod_cfg_id = intval($_POST['mod_cfg_id'] ?? 0);
|
||||
|
||||
// Clear all current defaults for this game first
|
||||
$db->query(
|
||||
"UPDATE `{$db_prefix}config_mods`
|
||||
SET is_default_for_billing = 0
|
||||
WHERE home_cfg_id = " . $save_home_cfg_id
|
||||
);
|
||||
|
||||
if ($save_mod_cfg_id > 0) {
|
||||
// Set the selected mod as default (only if it belongs to this game)
|
||||
$updated = $db->query(
|
||||
"UPDATE `{$db_prefix}config_mods`
|
||||
SET is_default_for_billing = 1
|
||||
WHERE mod_cfg_id = " . $save_mod_cfg_id . "
|
||||
AND home_cfg_id = " . $save_home_cfg_id
|
||||
);
|
||||
$saveMsg = $updated
|
||||
? "<div class='success'><p>Default mod/build updated for game config #{$save_home_cfg_id}.</p></div>"
|
||||
: "<div class='failure'><p>Failed to update default — mod may not belong to this game.</p></div>";
|
||||
} else {
|
||||
$saveMsg = "<div class='info'><p>Default cleared for game config #{$save_home_cfg_id}. Billing will use the service-specific mod or fail with an admin-visible error if none is set.</p></div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo $saveMsg;
|
||||
echo "<h2>Game Mod/Build Defaults for Billing</h2>";
|
||||
echo "<p>Mark one mod/build per game as the auto-install default used when billing provisions a new server. "
|
||||
. "This is used when a billing service does not specify its own mod (mod_cfg_id = 0).</p>";
|
||||
echo "<p><strong>Priority order during provisioning:</strong> "
|
||||
. "1) Service-specific mod_cfg_id → 2) is_default_for_billing here → "
|
||||
. "3) Single available mod (auto-selected) → 4) Fail with admin-visible error.</p>";
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Load all game configs that have at least one mod defined
|
||||
// -------------------------------------------------------------------
|
||||
$games = $db->resultQuery(
|
||||
"SELECT ch.home_cfg_id, ch.home_name
|
||||
FROM `{$db_prefix}config_homes` ch
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM `{$db_prefix}config_mods` cm
|
||||
WHERE cm.home_cfg_id = ch.home_cfg_id
|
||||
)
|
||||
ORDER BY ch.home_name ASC"
|
||||
);
|
||||
|
||||
if (empty($games)) {
|
||||
echo "<div class='info'><p>No game configurations with mods found. Add mods via Admin → Game Manager → Configure Games.</p></div>";
|
||||
return;
|
||||
}
|
||||
|
||||
echo "<table class='tablesorter' style='width:100%;'>";
|
||||
echo "<thead><tr><th>Game</th><th>home_cfg_id</th><th>Available Mods/Builds</th><th>Current Default</th><th>Action</th></tr></thead><tbody>";
|
||||
|
||||
foreach ((array)$games as $game) {
|
||||
$hcfgid = intval($game['home_cfg_id']);
|
||||
$gameName = htmlspecialchars($game['home_name'] ?? "Game #{$hcfgid}");
|
||||
|
||||
// Load mods for this game
|
||||
$mods = $db->resultQuery(
|
||||
"SELECT mod_cfg_id, mod_key, mod_name, is_default_for_billing
|
||||
FROM `{$db_prefix}config_mods`
|
||||
WHERE home_cfg_id = " . $hcfgid . "
|
||||
ORDER BY mod_name ASC"
|
||||
);
|
||||
|
||||
if (empty($mods)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentDefault = null;
|
||||
foreach ($mods as $m) {
|
||||
if (!empty($m['is_default_for_billing'])) {
|
||||
$currentDefault = htmlspecialchars($m['mod_name'] . ' (mod_cfg_id=' . $m['mod_cfg_id'] . ')');
|
||||
}
|
||||
}
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td><strong>{$gameName}</strong></td>";
|
||||
echo "<td>{$hcfgid}</td>";
|
||||
echo "<td>";
|
||||
$modNames = array_map(fn($m) => htmlspecialchars($m['mod_name']), $mods);
|
||||
echo implode('<br>', $modNames);
|
||||
echo "</td>";
|
||||
echo "<td>";
|
||||
echo $currentDefault
|
||||
? "<span style='color:green;'>✓ " . $currentDefault . "</span>"
|
||||
: "<span style='color:#999;'>None</span>";
|
||||
echo "</td>";
|
||||
echo "<td>";
|
||||
|
||||
// Form to set default
|
||||
echo "<form method='post' action='home.php?m=billing&p=admin_game_defaults' style='white-space:nowrap;'>";
|
||||
echo "<input type='hidden' name='save_default' value='1'>";
|
||||
echo "<input type='hidden' name='home_cfg_id' value='{$hcfgid}'>";
|
||||
echo "<select name='mod_cfg_id'>";
|
||||
echo "<option value='0'>(No default / clear)</option>";
|
||||
foreach ($mods as $m) {
|
||||
$sel = !empty($m['is_default_for_billing']) ? " selected" : "";
|
||||
echo "<option value='" . intval($m['mod_cfg_id']) . "'{$sel}>"
|
||||
. htmlspecialchars($m['mod_name'])
|
||||
. " [" . htmlspecialchars($m['mod_key']) . "]"
|
||||
. "</option>";
|
||||
}
|
||||
echo "</select> ";
|
||||
echo "<button type='submit' class='btn btn-sm'>Save</button>";
|
||||
echo "</form>";
|
||||
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Show billing_services with their current mod_cfg_id
|
||||
// -------------------------------------------------------------------
|
||||
echo "<div style='margin-top:30px;'>";
|
||||
echo "<h3>Billing Services — Mod/Build Override</h3>";
|
||||
echo "<p>Services below have an explicit <code>mod_cfg_id</code> set. This takes priority over the game default above. "
|
||||
. "Set to 0 to fall back to the game default.</p>";
|
||||
|
||||
$services = $db->resultQuery(
|
||||
"SELECT s.service_id, s.service_name, s.home_cfg_id, s.mod_cfg_id,
|
||||
ch.home_name AS game_name,
|
||||
cm.mod_name
|
||||
FROM `{$db_prefix}billing_services` s
|
||||
LEFT JOIN `{$db_prefix}config_homes` ch ON ch.home_cfg_id = s.home_cfg_id
|
||||
LEFT JOIN `{$db_prefix}config_mods` cm ON cm.mod_cfg_id = s.mod_cfg_id
|
||||
ORDER BY s.service_name ASC"
|
||||
);
|
||||
|
||||
if (empty($services)) {
|
||||
echo "<p style='color:#999;'>No billing services configured.</p>";
|
||||
} else {
|
||||
echo "<table class='tablesorter' style='width:100%;'>";
|
||||
echo "<thead><tr><th>Service</th><th>Game</th><th>Current mod_cfg_id</th><th>Mod Name</th></tr></thead><tbody>";
|
||||
foreach ((array)$services as $svc) {
|
||||
echo "<tr>";
|
||||
echo "<td>".htmlspecialchars($svc['service_name'] ?? '')." (#".intval($svc['service_id']).")</td>";
|
||||
echo "<td>".htmlspecialchars($svc['game_name'] ?? 'N/A')."</td>";
|
||||
echo "<td>".intval($svc['mod_cfg_id']).(intval($svc['mod_cfg_id']) === 0 ? " <em>(use game default)</em>" : "")."</td>";
|
||||
echo "<td>".htmlspecialchars($svc['mod_name'] ?? ($svc['mod_cfg_id'] == 0 ? '—' : 'mod not found'))."</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</tbody></table>";
|
||||
echo "<p><small>To change a service's mod, edit it in Admin → Billing → Services.</small></p>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db, $view;
|
||||
global $db, $view, $table_prefix;
|
||||
$db_prefix = isset($table_prefix) ? $table_prefix : '';
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$isAdmin = $db->isAdmin($user_id);
|
||||
|
||||
|
|
@ -14,7 +15,28 @@ function exec_ogp_module()
|
|||
echo "<div class='failure'><p>Access Denied: Admin privileges required.</p></div>";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Handle "Retry Provisioning" for a single order
|
||||
// -------------------------------------------------------------------
|
||||
if (isset($_POST['retry_provision_order']) && !empty($_POST['retry_order_id'])) {
|
||||
$retry_id = intval($_POST['retry_order_id']);
|
||||
require_once __DIR__ . '/create_servers.php';
|
||||
$retryResult = billing_invoke_provision([
|
||||
'order_ids' => [$retry_id],
|
||||
'user_id' => $user_id,
|
||||
'is_admin' => true,
|
||||
]);
|
||||
if (!empty($retryResult['provisioned_count'])) {
|
||||
echo "<div class='success'><p>Retry provisioning succeeded for order #{$retry_id}.</p></div>";
|
||||
} elseif (!empty($retryResult['output'])) {
|
||||
echo "<div class='failure'><p>Retry provisioning for order #{$retry_id} did not succeed. See details below.</p>"
|
||||
. "<pre>" . htmlspecialchars($retryResult['output']) . "</pre></div>";
|
||||
} else {
|
||||
echo "<div class='failure'><p>Retry provisioning for order #{$retry_id}: no result returned. Check server logs.</p></div>";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle bulk actions
|
||||
if (isset($_POST['bulk_action']) && isset($_POST['selected_orders'])) {
|
||||
$action = $_POST['bulk_action'];
|
||||
|
|
@ -30,13 +52,13 @@ function exec_ogp_module()
|
|||
exit;
|
||||
break;
|
||||
case 'expire':
|
||||
$db->query("UPDATE OGP_DB_PREFIXbilling_orders SET status='Expired' WHERE order_id=".$order_id);
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders` SET status='Expired' WHERE order_id=".$order_id);
|
||||
break;
|
||||
case 'activate':
|
||||
$db->query("UPDATE OGP_DB_PREFIXbilling_orders SET status='Active' WHERE order_id=".$order_id);
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders` SET status='Active' WHERE order_id=".$order_id);
|
||||
break;
|
||||
case 'invoice':
|
||||
$db->query("UPDATE OGP_DB_PREFIXbilling_orders SET status='Invoiced' WHERE order_id=".$order_id);
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders` SET status='Invoiced' WHERE order_id=".$order_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -66,9 +88,9 @@ function exec_ogp_module()
|
|||
|
||||
// Build query
|
||||
$query = "SELECT o.*, s.service_name, u.users_login, u.users_email
|
||||
FROM OGP_DB_PREFIXbilling_orders o
|
||||
LEFT JOIN OGP_DB_PREFIXbilling_services s ON o.service_id = s.service_id
|
||||
LEFT JOIN OGP_DB_PREFIXusers u ON o.user_id = u.user_id
|
||||
FROM `{$db_prefix}billing_orders` o
|
||||
LEFT JOIN `{$db_prefix}billing_services` s ON o.service_id = s.service_id
|
||||
LEFT JOIN `{$db_prefix}users` u ON o.user_id = u.user_id
|
||||
WHERE 1=1";
|
||||
|
||||
if ($status_filter != 'all') {
|
||||
|
|
@ -91,6 +113,17 @@ function exec_ogp_module()
|
|||
echo "<div class='info'><p>No orders found matching your filters.</p></div>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-fetch provisioning error counts per order for display
|
||||
$errorCounts = [];
|
||||
$errCountRows = $db->resultQuery(
|
||||
"SELECT billing_order_id, COUNT(*) AS cnt
|
||||
FROM `{$db_prefix}billing_provisioning_errors`
|
||||
GROUP BY billing_order_id"
|
||||
);
|
||||
foreach ((array)$errCountRows as $ecr) {
|
||||
$errorCounts[intval($ecr['billing_order_id'])] = intval($ecr['cnt']);
|
||||
}
|
||||
|
||||
echo "<form method='post' action='home.php?m=billing&p=admin_orders'>";
|
||||
echo "<div style='margin-bottom: 10px;'>";
|
||||
|
|
@ -130,39 +163,83 @@ function exec_ogp_module()
|
|||
case 'Expired': $status_class = 'label-danger'; break;
|
||||
default: $status_class = 'label-info';
|
||||
}
|
||||
|
||||
$oid = intval($order['order_id']);
|
||||
$errCount = $errorCounts[$oid] ?? 0;
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td><input type='checkbox' name='selected_orders[]' value='".$order['order_id']."'></td>";
|
||||
echo "<td>".$order['order_id']."</td>";
|
||||
echo "<td>".$order['users_login']."<br><small>".$order['users_email']."</small></td>";
|
||||
echo "<td>".$order['home_name']."</td>";
|
||||
echo "<td>".$order['service_name']."</td>";
|
||||
echo "<td><input type='checkbox' name='selected_orders[]' value='".$oid."'></td>";
|
||||
echo "<td>".$oid."</td>";
|
||||
echo "<td>".htmlspecialchars($order['users_login'] ?? '')."<br><small>".htmlspecialchars($order['users_email'] ?? '')."</small></td>";
|
||||
echo "<td>".htmlspecialchars($order['home_name'] ?? '')."</td>";
|
||||
echo "<td>".htmlspecialchars($order['service_name'] ?? '')."</td>";
|
||||
echo "<td>".$order['max_players']."</td>";
|
||||
echo "<td>$".number_format($order['price'], 2)."</td>";
|
||||
echo "<td>".$order['qty']." ".$order['invoice_duration']."(s)</td>";
|
||||
echo "<td><span class='label ".$status_class."'>".$order['status']."</span></td>";
|
||||
echo "<td><span class='label ".$status_class."'>".$order['status']."</span>";
|
||||
if ($errCount > 0) {
|
||||
echo " <span class='label label-warning' title='Provisioning errors'>" . $errCount . " error(s)</span>";
|
||||
}
|
||||
echo "</td>";
|
||||
echo "<td>".date('Y-m-d H:i', strtotime($order['order_date']))."</td>";
|
||||
echo "<td>".($order['end_date'] ? date('Y-m-d', strtotime($order['end_date'])) : 'N/A')."</td>";
|
||||
echo "<td>".($order['home_id'] ? $order['home_id'] : 'N/A')."</td>";
|
||||
echo "<td>";
|
||||
|
||||
if ($order['status'] == 'Active' && !$order['home_id']) {
|
||||
echo "<a href='home.php?m=billing&p=provision_servers&order_id=".$order['order_id']."' class='btn btn-sm'>Provision</a> ";
|
||||
echo "<a href='home.php?m=billing&p=provision_servers&order_id=".$oid."' class='btn btn-sm'>Provision</a> ";
|
||||
// Retry provisioning button (inline POST form)
|
||||
echo "<form method='post' action='home.php?m=billing&p=admin_orders' style='display:inline;'>";
|
||||
echo "<input type='hidden' name='retry_provision_order' value='1'>";
|
||||
echo "<input type='hidden' name='retry_order_id' value='".$oid."'>";
|
||||
echo "<button type='submit' class='btn btn-sm btn-warning'>Retry Provisioning</button>";
|
||||
echo "</form> ";
|
||||
}
|
||||
|
||||
if ($order['status'] == 'Active' && $order['home_id']) {
|
||||
echo "<a href='home.php?m=gamemanager&p=game_monitor&home_id-mod_id-ip=".$order['home_id']."' class='btn btn-sm'>View Server</a> ";
|
||||
}
|
||||
|
||||
echo "<a href='#' onclick='viewOrder(".$order['order_id'].")' class='btn btn-sm'>Details</a>";
|
||||
|
||||
if ($errCount > 0) {
|
||||
echo "<a href='#' onclick='toggleErrors(".$oid.")' class='btn btn-sm btn-danger'>Errors</a> ";
|
||||
}
|
||||
|
||||
echo "<a href='#' onclick='viewOrder(".$oid.")' class='btn btn-sm'>Details</a>";
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
|
||||
// Collapsible provisioning error rows
|
||||
if ($errCount > 0) {
|
||||
echo "<tr id='errors_".$oid."' style='display:none;background:#fff8f8;'>";
|
||||
echo "<td colspan='13'>";
|
||||
$errRows = $db->resultQuery(
|
||||
"SELECT * FROM `{$db_prefix}billing_provisioning_errors`
|
||||
WHERE billing_order_id=" . $oid . "
|
||||
ORDER BY created_at DESC LIMIT 20"
|
||||
);
|
||||
if (!empty($errRows)) {
|
||||
echo "<table style='width:100%;font-size:0.9em;'>";
|
||||
echo "<thead><tr><th>Time</th><th>Remote Srv</th><th>IP ID</th><th>Port</th><th>Mod</th><th>Message</th></tr></thead><tbody>";
|
||||
foreach ($errRows as $er) {
|
||||
echo "<tr>";
|
||||
echo "<td>".htmlspecialchars($er['created_at'])."</td>";
|
||||
echo "<td>".intval($er['remote_server_id'])."</td>";
|
||||
echo "<td>".intval($er['ip_id'])."</td>";
|
||||
echo "<td>".intval($er['attempted_port'])."</td>";
|
||||
echo "<td>".intval($er['mod_cfg_id'])."</td>";
|
||||
echo "<td>".htmlspecialchars($er['failure_message'])."</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</tbody></table>";
|
||||
}
|
||||
echo "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
echo "</form>";
|
||||
|
||||
// JavaScript for checkbox toggle
|
||||
// JavaScript for checkbox toggle and error panel
|
||||
echo "<script>
|
||||
function toggleAll(checkbox) {
|
||||
var checkboxes = document.getElementsByName('selected_orders[]');
|
||||
|
|
@ -170,6 +247,14 @@ function exec_ogp_module()
|
|||
checkboxes[i].checked = checkbox.checked;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleErrors(orderId) {
|
||||
var row = document.getElementById('errors_' + orderId);
|
||||
if (row) {
|
||||
row.style.display = (row.style.display === 'none') ? 'table-row' : 'none';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function viewOrder(orderId) {
|
||||
alert('Order details for #' + orderId + '\\n\\nFull order details feature coming soon.');
|
||||
|
|
@ -178,9 +263,11 @@ function exec_ogp_module()
|
|||
</script>";
|
||||
|
||||
// Summary stats
|
||||
$stats = $db->resultQuery("SELECT status, COUNT(*) as count, SUM(price) as total
|
||||
FROM OGP_DB_PREFIXbilling_orders
|
||||
GROUP BY status");
|
||||
$stats = $db->resultQuery(
|
||||
"SELECT status, COUNT(*) as count, SUM(price) as total
|
||||
FROM `{$db_prefix}billing_orders`
|
||||
GROUP BY status"
|
||||
);
|
||||
|
||||
echo "<div style='margin-top: 30px;'>";
|
||||
echo "<h3>Order Statistics</h3>";
|
||||
|
|
@ -204,8 +291,8 @@ function exec_ogp_module()
|
|||
// are the reason the game monitor may show "No expiration date found".
|
||||
$orphans = $db->resultQuery(
|
||||
"SELECT o.order_id, o.user_id, o.home_name, o.home_id, o.status, o.end_date
|
||||
FROM OGP_DB_PREFIXbilling_orders o
|
||||
LEFT JOIN OGP_DB_PREFIXserver_homes sh ON sh.home_id = o.home_id
|
||||
FROM `{$db_prefix}billing_orders` o
|
||||
LEFT JOIN `{$db_prefix}server_homes` sh ON sh.home_id = o.home_id
|
||||
WHERE o.home_id != '0'
|
||||
AND o.home_id != ''
|
||||
AND sh.home_id IS NULL
|
||||
|
|
@ -214,9 +301,9 @@ function exec_ogp_module()
|
|||
|
||||
echo "<div style='margin-top: 30px;'>";
|
||||
echo "<h3>Orphaned home_id Diagnostics</h3>";
|
||||
echo "<p style='color:#666;'>Billing orders that reference a <code>home_id</code> which no longer exists in <code>gsp_server_homes</code>. ";
|
||||
echo "<p style='color:#666;'>Billing orders that reference a <code>home_id</code> which no longer exists in <code>server_homes</code>. ";
|
||||
echo "These orders will not show an expiration date on the game monitor. ";
|
||||
echo "Reset <code>home_id</code> to <code>0</code> or re-provision these orders to fix them. ";
|
||||
echo "Reset <code>home_id</code> to <code>0</code> and use the Retry Provisioning button to re-provision them. ";
|
||||
echo "Run <code>normalize_billing_order_status.sql</code> to standardize any legacy status values.</p>";
|
||||
|
||||
if (empty($orphans)) {
|
||||
|
|
@ -237,5 +324,39 @@ function exec_ogp_module()
|
|||
echo "</tbody></table>";
|
||||
}
|
||||
echo "</div>";
|
||||
|
||||
// Recent provisioning errors (all orders) ————————————————————————————
|
||||
$recentErrors = $db->resultQuery(
|
||||
"SELECT e.*, o.home_name, u.users_login
|
||||
FROM `{$db_prefix}billing_provisioning_errors` e
|
||||
LEFT JOIN `{$db_prefix}billing_orders` o ON o.order_id = e.billing_order_id
|
||||
LEFT JOIN `{$db_prefix}users` u ON u.user_id = e.user_id
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT 50"
|
||||
);
|
||||
|
||||
echo "<div style='margin-top: 30px;'>";
|
||||
echo "<h3>Recent Provisioning Errors</h3>";
|
||||
if (empty($recentErrors)) {
|
||||
echo "<p style='color:green;'>✓ No provisioning errors recorded.</p>";
|
||||
} else {
|
||||
echo "<table class='tablesorter' style='width:100%;'>";
|
||||
echo "<thead><tr><th>Time</th><th>Order ID</th><th>User</th><th>Server Name</th><th>Remote Srv</th><th>IP ID</th><th>Port</th><th>Mod</th><th>Message</th></tr></thead><tbody>";
|
||||
foreach ($recentErrors as $er) {
|
||||
echo "<tr>";
|
||||
echo "<td>".htmlspecialchars($er['created_at'])."</td>";
|
||||
echo "<td>".intval($er['billing_order_id'])."</td>";
|
||||
echo "<td>".htmlspecialchars($er['users_login'] ?? ('uid:'.intval($er['user_id'])))."</td>";
|
||||
echo "<td>".htmlspecialchars($er['home_name'] ?? '')."</td>";
|
||||
echo "<td>".intval($er['remote_server_id'])."</td>";
|
||||
echo "<td>".intval($er['ip_id'])."</td>";
|
||||
echo "<td>".intval($er['attempted_port'])."</td>";
|
||||
echo "<td>".intval($er['mod_cfg_id'])."</td>";
|
||||
echo "<td>".htmlspecialchars($er['failure_message'])."</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</tbody></table>";
|
||||
}
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,584 @@ if (!function_exists('billing_invoke_provision')) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a provisioning failure to billing_provisioning_errors.
|
||||
* All parameters are sanitised inside this function.
|
||||
*/
|
||||
if (!function_exists('billing_log_provision_error')) {
|
||||
function billing_log_provision_error(
|
||||
$db,
|
||||
string $db_prefix,
|
||||
int $billing_order_id,
|
||||
int $home_id,
|
||||
int $user_id,
|
||||
int $remote_server_id,
|
||||
int $ip_id,
|
||||
int $attempted_port,
|
||||
int $mod_cfg_id,
|
||||
string $failure_message
|
||||
): void {
|
||||
try {
|
||||
$db->query(
|
||||
"INSERT INTO `{$db_prefix}billing_provisioning_errors`
|
||||
(`billing_order_id`,`home_id`,`user_id`,`remote_server_id`,`ip_id`,`attempted_port`,`mod_cfg_id`,`failure_message`,`created_at`)
|
||||
VALUES ("
|
||||
. intval($billing_order_id) . ","
|
||||
. intval($home_id) . ","
|
||||
. intval($user_id) . ","
|
||||
. intval($remote_server_id) . ","
|
||||
. intval($ip_id) . ","
|
||||
. intval($attempted_port) . ","
|
||||
. intval($mod_cfg_id) . ","
|
||||
. "'" . $db->realEscapeSingle($failure_message) . "',"
|
||||
. "NOW())"
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
// Never let logging itself break provisioning
|
||||
error_log('billing_log_provision_error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db,$view,$settings,$table_prefix;
|
||||
$db_prefix = isset($table_prefix) ? $table_prefix : '';
|
||||
|
||||
// $now is used in multiple branches below — define it once here so it is
|
||||
// always a string that date() / strtotime() can handle safely (PHP 8 fix).
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$override = isset($GLOBALS['BILLING_PROVISION_OVERRIDE']) ? $GLOBALS['BILLING_PROVISION_OVERRIDE'] : null;
|
||||
$user_id = isset($override['user_id']) ? intval($override['user_id']) : (isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0);
|
||||
$isAdmin = isset($override['is_admin']) ? (bool)$override['is_admin'] : $db->isAdmin($user_id);
|
||||
$provision_all = $override ? !empty($override['provision_all']) : isset($_POST['provision_all']);
|
||||
$orderIds = array();
|
||||
if ($override && !empty($override['order_ids'])) {
|
||||
$orderIds = array_map('intval', (array)$override['order_ids']);
|
||||
}
|
||||
if (empty($orderIds)) {
|
||||
$order_id = null;
|
||||
if (isset($_POST['order_id'])) {
|
||||
$order_id = $_POST['order_id'];
|
||||
}
|
||||
if(isset($_GET['order_id'])){
|
||||
$order_id = $_GET['order_id'];
|
||||
}
|
||||
if (!empty($order_id)) {
|
||||
$orderIds = array(intval($order_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle provision_all request - provision all Active (paid) orders for this user
|
||||
if ($provision_all) {
|
||||
if ( $isAdmin ){
|
||||
$orders = $db->resultQuery( "SELECT * FROM `{$db_prefix}billing_orders` WHERE status='Active' AND (home_id='0' OR home_id='') ORDER BY order_id" );
|
||||
} else {
|
||||
$orders = $db->resultQuery( "SELECT * FROM `{$db_prefix}billing_orders` WHERE user_id=".$db->realEscapeSingle($user_id)." AND status='Active' AND (home_id='0' OR home_id='') ORDER BY order_id" );
|
||||
}
|
||||
}
|
||||
// Handle provision_single or order_id parameter - provision specific order
|
||||
else {
|
||||
if (empty($orderIds)) {
|
||||
echo "<div class='failure'>No order ID specified.</div>";
|
||||
$GLOBALS['BILLING_PROVISION_LAST_RESULT'] = array('provisioned_count'=>0,'failed_count'=>0,'orders'=>array());
|
||||
return;
|
||||
}
|
||||
$idList = implode(',', array_map('intval', $orderIds));
|
||||
if ( $isAdmin ){
|
||||
$orders = $db->resultQuery( "SELECT * FROM `{$db_prefix}billing_orders` WHERE order_id IN ($idList) AND status='Active'" );
|
||||
} else {
|
||||
$orders = $db->resultQuery( "SELECT * FROM `{$db_prefix}billing_orders` WHERE order_id IN ($idList) AND user_id=".$db->realEscapeSingle($user_id)." AND status='Active'" );
|
||||
}
|
||||
}
|
||||
$processed_orders = array();
|
||||
if( !empty($orders) )
|
||||
{
|
||||
$provisioned_count = 0;
|
||||
$failed_count = 0;
|
||||
|
||||
foreach ((array)$orders as $order)
|
||||
{
|
||||
$end_date = null;
|
||||
$end_date_str = null;
|
||||
$order_id = $order['order_id'];
|
||||
$processed_orders[] = intval($order_id);
|
||||
$service_id = $order['service_id'];
|
||||
$home_name = $order['home_name'];
|
||||
$remote_control_password = $order['remote_control_password'];
|
||||
$ftp_password = $order['ftp_password'];
|
||||
if ($remote_control_password === '' || strcasecmp((string)$remote_control_password, 'ChangeMe') === 0) {
|
||||
$remote_control_password = billing_generate_provision_password();
|
||||
}
|
||||
if ($ftp_password === '' || strcasecmp((string)$ftp_password, 'ChangeMe') === 0) {
|
||||
$ftp_password = billing_generate_provision_password();
|
||||
}
|
||||
$ip = $order['ip'];
|
||||
$max_players = $order['max_players'];
|
||||
$user_id = $order['user_id'];
|
||||
$extended = isset($order['extended']) && $order['extended'] == "1" ? TRUE : FALSE;
|
||||
$alreadyProvisioned = !$extended && intval($order['home_id'] ?? 0) > 0;
|
||||
//Query service info
|
||||
$service = $db->resultQuery( "SELECT *
|
||||
FROM `{$db_prefix}billing_services`
|
||||
WHERE service_id=".$db->realEscapeSingle($service_id) );
|
||||
|
||||
if( !empty( $service[0] ) )
|
||||
{
|
||||
$home_cfg_id = $service[0]['home_cfg_id'];
|
||||
$mod_cfg_id = $service[0]['mod_cfg_id'];
|
||||
//remote_server_id has been stored in IP_ID
|
||||
//$remote_server_id = $service[0]['remote_server_id'];
|
||||
$remote_server_id = $order['ip'];
|
||||
|
||||
$ftp = $service[0]['ftp'];
|
||||
$install_method = $service[0]['install_method'];
|
||||
$manual_url = $service[0]['manual_url'];
|
||||
$access_rights = $service[0]['access_rights'];
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if($alreadyProvisioned)
|
||||
{
|
||||
$home_id = intval($order['home_id']);
|
||||
}
|
||||
elseif($extended)
|
||||
{
|
||||
$home_id = $order['home_id'];
|
||||
|
||||
//Get The home info without mods in 1 array (Necesary for remote connection).
|
||||
$home_info = $db->getGameHomeWithoutMods($home_id);
|
||||
|
||||
//Create the remote connection
|
||||
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
|
||||
|
||||
//Reassign the server
|
||||
$db->assignHomeTo( "user", $user_id, $home_id, $access_rights );
|
||||
|
||||
//Reenable the FTP account
|
||||
if ($ftp == "enabled")
|
||||
{
|
||||
$remote->ftp_mgr("useradd", $home_info['home_id'], $home_info['ftp_password'], $home_info['home_path']);
|
||||
$db->changeFtpStatus('enabled',$home_info['home_id']);
|
||||
}
|
||||
echo "<h4>Server Installed, Check your Email for Details</h4><br>";
|
||||
|
||||
//Panel Log
|
||||
$db->logger( "RENEWED SERVER " . $home_id);
|
||||
// SEND EMAIL
|
||||
$settings = $db->getSettings();
|
||||
$subject = "Gameserver Renewel at " . $settings['panel_name'];
|
||||
$email = $db->resultQuery(" SELECT DISTINCT users_email
|
||||
FROM {$table_prefix}users, {$table_prefix}billing_orders
|
||||
WHERE {$table_prefix}users.user_id = $user_id")[0]["users_email"];
|
||||
|
||||
$message = "Your server, " . $home_name ." ID #". $home_id . " at " . $settings['panel_name'] . " has just been renewed.<br>
|
||||
Thank You for your continued support.<br>
|
||||
If you have any questions or requests, visit our website or contact us directly in our Discord Server.";
|
||||
|
||||
$mail = mymail($email, $subject, $message, $settings);
|
||||
$rundate = date('d/M/y G:i', is_numeric($now) ? (int)$now : strtotime($now));
|
||||
|
||||
if (!$mail)
|
||||
$db->logger( "Email FAILED - Server Renewed " . $home_id);
|
||||
// END EMAIL
|
||||
|
||||
//WEBHOOK Discord
|
||||
discordmsg(array('content' => "The ". $home_name ." server ID #". $home_id . " has just been renewed."), $settings['discord_webhook_main'] ?? '');
|
||||
//end WEBHOOK Discord
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//OPTIONS, change it at your choice;
|
||||
$extra_params = "";//no extra params defined by default
|
||||
$cpu_affinity = "NA";//Affinity to one core/thread of the cpu by number, use NA to disable it
|
||||
$nice = "0";//Min priority=19 Max Priority=-19
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Resolve IP: find the first IP address configured for this
|
||||
// remote server. The order.ip column stores remote_server_id.
|
||||
// ---------------------------------------------------------------
|
||||
$resolved_remote_server_id = intval($remote_server_id);
|
||||
$ip_id = null;
|
||||
$ipRows = $db->resultQuery(
|
||||
"SELECT ip_id FROM `{$db_prefix}remote_server_ips`
|
||||
WHERE remote_server_id=" . $resolved_remote_server_id . "
|
||||
ORDER BY ip_id ASC LIMIT 1"
|
||||
);
|
||||
if (!empty($ipRows[0]['ip_id'])) {
|
||||
$ip_id = intval($ipRows[0]['ip_id']);
|
||||
}
|
||||
if ($ip_id === null) {
|
||||
$errMsg = "No IP address configured for remote server ID {$resolved_remote_server_id} (order_id={$order_id}). "
|
||||
. "Please add an IP to that remote server in the panel.";
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), 0, intval($user_id), $resolved_remote_server_id, 0, 0, intval($mod_cfg_id), $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Resolve mod/build in priority order:
|
||||
// 1. Explicit mod_cfg_id from billing_services (if > 0 and valid)
|
||||
// 2. Admin-configured is_default_for_billing on config_mods
|
||||
// 3. Only one mod available for this game — use it automatically
|
||||
// 4. Fail gracefully with an admin-visible error
|
||||
// ---------------------------------------------------------------
|
||||
$resolved_mod_cfg_id = null;
|
||||
|
||||
if (!empty($mod_cfg_id) && intval($mod_cfg_id) > 0) {
|
||||
$modCheck = $db->resultQuery(
|
||||
"SELECT mod_cfg_id FROM `{$db_prefix}config_mods`
|
||||
WHERE mod_cfg_id=" . intval($mod_cfg_id) . "
|
||||
AND home_cfg_id=" . intval($home_cfg_id)
|
||||
);
|
||||
if (!empty($modCheck[0]['mod_cfg_id'])) {
|
||||
$resolved_mod_cfg_id = intval($modCheck[0]['mod_cfg_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolved_mod_cfg_id === null) {
|
||||
$defaultModRow = $db->resultQuery(
|
||||
"SELECT mod_cfg_id FROM `{$db_prefix}config_mods`
|
||||
WHERE home_cfg_id=" . intval($home_cfg_id) . "
|
||||
AND is_default_for_billing=1
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!empty($defaultModRow[0]['mod_cfg_id'])) {
|
||||
$resolved_mod_cfg_id = intval($defaultModRow[0]['mod_cfg_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolved_mod_cfg_id === null) {
|
||||
$allMods = $db->resultQuery(
|
||||
"SELECT mod_cfg_id FROM `{$db_prefix}config_mods`
|
||||
WHERE home_cfg_id=" . intval($home_cfg_id)
|
||||
);
|
||||
if (!empty($allMods) && count($allMods) === 1) {
|
||||
$resolved_mod_cfg_id = intval($allMods[0]['mod_cfg_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolved_mod_cfg_id === null) {
|
||||
$errMsg = "No default mod/build configured for game type (home_cfg_id={$home_cfg_id}, order_id={$order_id}). "
|
||||
. "Visit Admin \u{2192} Game Defaults to mark a mod/build as the billing default.";
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), 0, intval($user_id), $resolved_remote_server_id, $ip_id, 0, 0, $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use resolved values for the rest of the provisioning flow
|
||||
$mod_cfg_id = $resolved_mod_cfg_id;
|
||||
|
||||
//Add Game home to database
|
||||
//HARD CODE TO /home/gameserver/
|
||||
$rserver = $db->getRemoteServer($resolved_remote_server_id);
|
||||
$game_path = "/home/gameserver/";
|
||||
$home_id = $db->addGameHome( $resolved_remote_server_id, $user_id, $home_cfg_id, $game_path, $home_name, $remote_control_password, $ftp_password);
|
||||
|
||||
if (!$home_id) {
|
||||
$errMsg = "Failed to create game home record for order_id={$order_id}, user_id={$user_id}, home_cfg_id={$home_cfg_id}.";
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), 0, intval($user_id), $resolved_remote_server_id, $ip_id, 0, $mod_cfg_id, $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Assign next available port to the new server home.
|
||||
// ---------------------------------------------------------------
|
||||
$next_port = $db->getNextAvailablePort($ip_id, $home_cfg_id);
|
||||
if ($next_port === false || $next_port === null) {
|
||||
$errMsg = "No available port for ip_id={$ip_id}, home_cfg_id={$home_cfg_id} (order_id={$order_id}). "
|
||||
. "Configure a port range for this IP/game type in the panel.";
|
||||
$db->deleteGameHome($home_id);
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), 0, intval($user_id), $resolved_remote_server_id, $ip_id, 0, $mod_cfg_id, $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$add_port = $db->addGameIpPort($home_id, $ip_id, $next_port);
|
||||
if (!$add_port) {
|
||||
$errMsg = "Failed to assign port {$next_port} to home_id={$home_id} (ip_id={$ip_id}, order_id={$order_id}).";
|
||||
$db->deleteGameHome($home_id);
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), 0, intval($user_id), $resolved_remote_server_id, $ip_id, $next_port, $mod_cfg_id, $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Assign the Game Mod to the Game Home
|
||||
$mod_id = $db->addModToGameHome( $home_id, $mod_cfg_id );
|
||||
if (!$mod_id) {
|
||||
$errMsg = "Failed to assign mod_cfg_id={$mod_cfg_id} to home_id={$home_id} (order_id={$order_id}). The mod may already be assigned or does not exist.";
|
||||
// Try to recover the mod_id if it already exists (e.g. duplicate provisioning attempt)
|
||||
$existingMod = $db->resultQuery(
|
||||
"SELECT mod_id FROM `{$db_prefix}game_mods`
|
||||
WHERE home_id=" . intval($home_id) . "
|
||||
AND mod_cfg_id=" . intval($mod_cfg_id) . "
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!empty($existingMod[0]['mod_id'])) {
|
||||
$mod_id = intval($existingMod[0]['mod_id']);
|
||||
} else {
|
||||
$db->delGameIpPort($home_id, $ip_id, $next_port);
|
||||
$db->deleteGameHome($home_id);
|
||||
billing_log_provision_error($db, $db_prefix, intval($order_id), intval($home_id), intval($user_id), $resolved_remote_server_id, $ip_id, $next_port, $mod_cfg_id, $errMsg);
|
||||
echo "<div class='failure'><p><strong>Provisioning failed for order #" . intval($order_id) . ":</strong> " . htmlspecialchars($errMsg) . "</p></div>";
|
||||
$failed_count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$db->updateGameModParams( $max_players, $extra_params, $cpu_affinity, $nice, $home_id, $mod_cfg_id );
|
||||
$db->assignHomeTo( "user", $user_id, $home_id, $access_rights );
|
||||
|
||||
//Get The home info without mods in 1 array (Necesary for remote connection).
|
||||
$home_info = $db->getGameHomeWithoutMods($home_id);
|
||||
|
||||
//Create the remote connection
|
||||
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
|
||||
|
||||
//Get Full home info in 1 array
|
||||
$home_info = $db->getGameHome($home_id);
|
||||
|
||||
//Read the Game Config from the XML file
|
||||
$server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$home_info['home_cfg_file']);
|
||||
|
||||
//Get Values from XML
|
||||
$modkey = $home_info['mods'][$mod_id]['mod_key'];
|
||||
$mod_xml = xml_get_mod($server_xml, $modkey);
|
||||
$installer_name = $mod_xml->installer_name;
|
||||
$mod_cfg_id = $home_info['mods'][$mod_id]['mod_cfg_id'];
|
||||
|
||||
//Get Preinstall commands from xml
|
||||
$precmd = $server_xml->pre_install;
|
||||
|
||||
|
||||
//Get Postinstall commands from xml
|
||||
$postcmd = $server_xml->post_install;
|
||||
|
||||
|
||||
//Enable FTP account in remote server
|
||||
if ($ftp == "enabled")
|
||||
{
|
||||
$remote->ftp_mgr("useradd", $home_info['home_id'], $home_info['ftp_password'], $home_info['home_path']);
|
||||
$db->changeFtpStatus('enabled',$home_info['home_id']);
|
||||
}
|
||||
|
||||
//Install files for this service in the remote server
|
||||
$exec_folder_path = clean_path($home_info['home_path'] . "/" . $server_xml->exe_location );
|
||||
$exec_path = clean_path($exec_folder_path . "/" . $server_xml->server_exec_name );
|
||||
|
||||
if ( (string)$server_xml->installer === "steamcmd" && !empty((string)$installer_name) )
|
||||
{
|
||||
if( preg_match("/win32/", $server_xml->game_key) OR preg_match("/win64/", $server_xml->game_key) )
|
||||
$cfg_os = "windows";
|
||||
elseif( preg_match("/linux/", $server_xml->game_key) )
|
||||
$cfg_os = "linux";
|
||||
|
||||
// Some games like L4D2 require anonymous login
|
||||
if($mod_xml->installer_login){
|
||||
$login = $mod_xml->installer_login;
|
||||
$pass = '';
|
||||
}else{
|
||||
$login = $settings['steam_user'];
|
||||
$pass = $settings['steam_pass'];
|
||||
}
|
||||
|
||||
$modname = ( $installer_name == '90' and !preg_match("/(cstrike|valve)/", $modkey) ) ? $modkey : '';
|
||||
$betaname = isset($mod_xml->betaname) ? $mod_xml->betaname : '';
|
||||
$betapwd = isset($mod_xml->betapwd) ? $mod_xml->betapwd : '';
|
||||
$arch = isset($mod_xml->steam_bitness) ? $mod_xml->steam_bitness : '';
|
||||
|
||||
$remote->steam_cmd( $home_id,$home_info['home_path'],$installer_name,$modname,
|
||||
$betaname,$betapwd,$login,$pass,$settings['steam_guard'],
|
||||
$exec_folder_path,$exec_path,$precmd,$postcmd,$cfg_os,'',$arch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No SteamCMD installer — run pre/post install scripts only.
|
||||
if (!empty((string)$precmd)) {
|
||||
$result = $remote->exec((string)$precmd);
|
||||
if ($result === NULL)
|
||||
$db->logger("Script-only install: pre_install script returned no output for home_id $home_id");
|
||||
}
|
||||
if (!empty((string)$postcmd)) {
|
||||
$result = $remote->exec((string)$postcmd);
|
||||
if ($result === NULL)
|
||||
$db->logger("Script-only install: post_install script returned no output for home_id $home_id");
|
||||
}
|
||||
}
|
||||
echo "<h4><br><p>".get_lang('starting_installations')."</p></h4><br>";
|
||||
//PANEL LOG
|
||||
$db->logger( "CREATED NEW SERVER " . $home_id);
|
||||
// SEND EMAIL to new server only
|
||||
if($order['end_date'] == 0){
|
||||
$settings = $db->getSettings();
|
||||
$subject = "New Gameserver installed at " . $settings['panel_name'];
|
||||
$email = $db->resultQuery(" SELECT DISTINCT users_email
|
||||
FROM {$table_prefix}users, {$table_prefix}billing_orders
|
||||
WHERE {$table_prefix}users.user_id = $user_id")[0]["users_email"];
|
||||
|
||||
$message = "Your server, " . $home_name ." ID #". $home_id . " at " . $settings['panel_name'] . " has just been created.<br>
|
||||
Thank You for your continued support.<br>
|
||||
If you have any questions or requests, visit our website or contact us directly in our Discord Server.
|
||||
You can login to the Game Panel and click on Game Monitor to see your server. <br><br>
|
||||
Thank you!<br> ";
|
||||
$mail = mymail($email, $subject, $message, $settings);
|
||||
$rundate = date('d/M/y G:i', is_numeric($now) ? (int)$now : strtotime($now));
|
||||
|
||||
if (!$mail)
|
||||
$db->logger( "Email FAILED - Server Created " . $home_id);
|
||||
|
||||
|
||||
//WEBHOOK Discord
|
||||
discordmsg(array('content' => "A new server, ". $home_name ." ID #". $home_id . ", has just been created."), $settings['discord_webhook_main'] ?? '');
|
||||
//end WEBHOOK Discord
|
||||
}
|
||||
// END EMAIL
|
||||
|
||||
|
||||
}
|
||||
// Set expiration date in panel database
|
||||
// Status values: Active (provisioned & current), Invoiced (renewal invoice open),
|
||||
// Expired (past due and awaiting deletion)
|
||||
// end_date / next_invoice_date: when the next renewal invoice should be generated
|
||||
if ($alreadyProvisioned)
|
||||
{
|
||||
$existing_end = strtotime((string)($order['end_date'] ?? ''));
|
||||
if ($existing_end === false || $existing_end <= 0) {
|
||||
$existing_end = time();
|
||||
}
|
||||
$end_date_str = date('Y-m-d H:i:s', $existing_end);
|
||||
}
|
||||
elseif ($order['invoice_duration'] == "day")
|
||||
{
|
||||
|
||||
if(empty($order['end_date']) || $order['end_date'] === NULL){
|
||||
$end_date = strtotime('+'.$order['qty'].' day');
|
||||
}
|
||||
else{
|
||||
//this is a renewel, start from end of previous order
|
||||
$current_end = strtotime($order['end_date']);
|
||||
if ($current_end === false) {
|
||||
$current_end = time(); // fallback to now if date is invalid
|
||||
}
|
||||
$end_date = strtotime('+'.$order['qty'].' day', $current_end);
|
||||
}
|
||||
|
||||
}
|
||||
elseif ($order['invoice_duration'] == "month")
|
||||
{
|
||||
// this is a new order
|
||||
if(empty($order['end_date']) || $order['end_date'] === NULL){
|
||||
$end_date = strtotime('+'.(intval($order['qty']) * 31).' day');
|
||||
|
||||
}
|
||||
else{
|
||||
//this is a renewel, start from end of previous order
|
||||
$current_end = strtotime($order['end_date']);
|
||||
if ($current_end === false) {
|
||||
$current_end = time(); // fallback to now if date is invalid
|
||||
}
|
||||
$end_date = strtotime('+'.(intval($order['qty']) * 31).' day', $current_end);
|
||||
}
|
||||
}
|
||||
elseif ($order['invoice_duration'] == "year")
|
||||
{
|
||||
// this is a new order
|
||||
if(empty($order['end_date']) || $order['end_date'] === NULL){
|
||||
$end_date = strtotime('+'.$order['qty'].' year');
|
||||
}
|
||||
else{
|
||||
//this is a renewel, start from end of previous order
|
||||
$current_end = strtotime($order['end_date']);
|
||||
if ($current_end === false) {
|
||||
$current_end = time(); // fallback to now if date is invalid
|
||||
}
|
||||
$end_date = strtotime('+'.$order['qty'].' year', $current_end);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (!isset($end_date_str)) {
|
||||
$end_date_str = date('Y-m-d H:i:s', $end_date);
|
||||
}
|
||||
|
||||
// Set order status to 'Active' (server provisioned and current)
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders`
|
||||
SET status='Active'
|
||||
WHERE order_id=".$db->realEscapeSingle($order_id));
|
||||
|
||||
// Set the order expiration / next renewal date
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders`
|
||||
SET end_date='" . $db->realEscapeSingle($end_date_str) . "',
|
||||
remote_control_password='" . $db->realEscapeSingle($remote_control_password) . "',
|
||||
ftp_password='" . $db->realEscapeSingle($ftp_password) . "'
|
||||
WHERE order_id=".$db->realEscapeSingle($order_id));
|
||||
|
||||
// Save home_id created by this order
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders`
|
||||
SET home_id='" . $db->realEscapeSingle($home_id) . "' WHERE order_id=".$db->realEscapeSingle($order_id));
|
||||
|
||||
$db->query("UPDATE `{$db_prefix}billing_invoices`
|
||||
SET home_id=" . $db->realEscapeSingle($home_id) . ",
|
||||
billing_status='Active'
|
||||
WHERE order_id=" . $db->realEscapeSingle($order_id));
|
||||
|
||||
$db->query("UPDATE `{$db_prefix}billing_transactions`
|
||||
SET home_id=" . $db->realEscapeSingle($home_id) . "
|
||||
WHERE invoice_id IN (SELECT invoice_id FROM `{$db_prefix}billing_invoices` WHERE order_id=" . $db->realEscapeSingle($order_id) . ")");
|
||||
|
||||
// Set billing_status and next_invoice_date on server_homes
|
||||
$db->query("UPDATE `{$db_prefix}server_homes`
|
||||
SET billing_status = 'Active',
|
||||
next_invoice_date = '" . $db->realEscapeSingle($end_date_str) . "',
|
||||
billing_enabled = 1
|
||||
WHERE home_id = " . $db->realEscapeSingle($home_id));
|
||||
|
||||
$provisioned_count++;
|
||||
|
||||
}
|
||||
|
||||
$db->query( "UPDATE `{$db_prefix}game_mods` SET max_players= ".$order['max_players']." WHERE home_id=".$db->realEscapeSingle($home_id));
|
||||
|
||||
// Show results and redirect
|
||||
if ($provisioned_count > 0) {
|
||||
echo "<div class='success'>";
|
||||
echo "<h3>Server Provisioning Complete</h3>";
|
||||
echo "<p>Successfully provisioned $provisioned_count server(s). Your server(s) are now active.</p>";
|
||||
echo "</div>";
|
||||
echo "<p><a href='home.php?m=gamemanager&p=game_monitor' class='btn'>View My Servers</a></p>";
|
||||
// Auto-redirect after 3 seconds
|
||||
echo "<script>setTimeout(function(){ window.location.href='home.php?m=gamemanager&p=game_monitor'; }, 3000);</script>";
|
||||
} else {
|
||||
echo "<div class='info'>";
|
||||
echo "<p>No servers to provision. All orders have already been processed.</p>";
|
||||
echo "</div>";
|
||||
echo "<p><a href='home.php?m=billing&p=my_orders' class='btn'>View My Orders</a></p>";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo "<div class='failure'>";
|
||||
echo "<p>No paid orders found to provision.</p>";
|
||||
echo "</div>";
|
||||
echo "<p><a href='home.php?m=billing&p=my_orders' class='btn'>View My Orders</a></p>";
|
||||
$provisioned_count = 0;
|
||||
$failed_count = 0;
|
||||
}
|
||||
$GLOBALS['BILLING_PROVISION_LAST_RESULT'] = array(
|
||||
'provisioned_count' => isset($provisioned_count) ? $provisioned_count : 0,
|
||||
'failed_count' => isset($failed_count) ? $failed_count : 0,
|
||||
'orders' => $processed_orders,
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db,$view,$settings,$table_prefix;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
// Module general information
|
||||
$module_title = "billing";
|
||||
$module_version = "3.6";
|
||||
$db_version = 7;
|
||||
$db_version = 8;
|
||||
$module_required = FALSE;
|
||||
// Module description
|
||||
$module_description = "Billing storefront / provisioning integration. Public ordering runs as a standalone site; panel pages provide provisioning and admin order management.";
|
||||
|
|
@ -422,4 +422,39 @@ $install_queries[7] = array(
|
|||
},
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// db_version 8 — Provisioning error logging and default mod/build support.
|
||||
// (a) billing_provisioning_errors: records every failed auto-provision
|
||||
// attempt so admins can diagnose port/mod issues without digging
|
||||
// through PHP logs.
|
||||
// (b) config_mods.is_default_for_billing: admins can mark exactly one
|
||||
// mod/build per game as the automatic billing install default.
|
||||
// Both changes are safe to re-run (IF NOT EXISTS / INFORMATION_SCHEMA).
|
||||
// -----------------------------------------------------------------------
|
||||
$install_queries[8] = array(
|
||||
// (a) Create billing_provisioning_errors table
|
||||
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."billing_provisioning_errors` (
|
||||
`error_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`billing_order_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`home_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`user_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`remote_server_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`ip_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`attempted_port` INT(11) NOT NULL DEFAULT 0,
|
||||
`mod_cfg_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`failure_message` TEXT NOT NULL,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`error_id`),
|
||||
KEY `billing_order_id` (`billing_order_id`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
// (b) Add is_default_for_billing to config_mods if missing
|
||||
function($db) {
|
||||
$r = $db->resultQuery("SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'OGP_DB_PREFIXconfig_mods' AND COLUMN_NAME = 'is_default_for_billing'");
|
||||
if ($r && isset($r[0]['cnt']) && (int)$r[0]['cnt'] > 0) return true;
|
||||
return (bool)$db->query("ALTER TABLE `OGP_DB_PREFIXconfig_mods` ADD `is_default_for_billing` TINYINT(1) NOT NULL DEFAULT 0");
|
||||
},
|
||||
);
|
||||
|
||||
?>
|
||||
|
|
@ -13,6 +13,10 @@ These pages are accessible within the panel for server provisioning and manageme
|
|||
<menu>My Orders</menu>
|
||||
</page>
|
||||
|
||||
<page key="admin_game_defaults" file="admin_game_defaults.php" access="admin">
|
||||
<menu>Game Mod Defaults</menu>
|
||||
</page>
|
||||
|
||||
<page key="admin_orders" file="admin_orders.php" access="admin">
|
||||
<menu>Manage All Orders</menu>
|
||||
</page>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue