diff --git a/modules/administration/panel_update.php b/modules/administration/panel_update.php index 1b822b5b..ed87a164 100644 --- a/modules/administration/panel_update.php +++ b/modules/administration/panel_update.php @@ -1098,14 +1098,14 @@ function gsp_panel_update_section() $finished_at = date('Y-m-d H:i:s'); if ($result['success']) { print_success( - 'Panel updated to development version (' . htmlspecialchars($stable_branch) . '). ' + 'Panel updated to GitHub Stable (' . htmlspecialchars($stable_branch) . '). ' . intval($result['files_copied']) . ' file(s) updated. Source: ' . htmlspecialchars($stable_branch) . '' ); - gsp_update_log("Admin {$user_label} updated panel to stable branch {$stable_branch}"); + gsp_update_log("Admin {$user_label} updated panel to GitHub Stable branch {$stable_branch}"); gsp_log_update_to_db( 'development', $stable_branch, 'success', - 'Updated to stable branch ' . $stable_branch . ' by ' . $_SESSION['users_login'], + 'Updated to GitHub Stable branch ' . $stable_branch . ' by ' . $_SESSION['users_login'], $result['backup_dir'] ?? null, isset($result['backup_dir']) ? $result['backup_dir'] . '/database.sql' : null, isset($result['backup_dir']) ? $result['backup_dir'] . '/panel-files.tar.gz': null, @@ -1113,10 +1113,10 @@ function gsp_panel_update_section() ); } else { print_failure('Update failed: ' . htmlspecialchars($result['error'])); - gsp_update_log("Admin {$user_label} update to stable branch {$stable_branch} FAILED: {$result['error']}"); + gsp_update_log("Admin {$user_label} update to GitHub Stable branch {$stable_branch} FAILED: {$result['error']}"); gsp_log_update_to_db( 'development', $stable_branch, 'failed', - 'Update to stable branch ' . $stable_branch . ' failed: ' . $result['error'], + 'Update to GitHub Stable branch ' . $stable_branch . ' failed: ' . $result['error'], null, null, null, $started_at, $finished_at ); } @@ -1127,14 +1127,14 @@ function gsp_panel_update_section() $finished_at = date('Y-m-d H:i:s'); if ($result['success']) { print_success( - 'Panel updated to cutting edge version (' . htmlspecialchars($unstable_branch) . '). ' + 'Panel updated to GitHub Unstable (' . htmlspecialchars($unstable_branch) . '). ' . intval($result['files_copied']) . ' file(s) updated. Source: ' . htmlspecialchars($unstable_branch) . '' ); - gsp_update_log("Admin {$user_label} updated panel to unstable branch {$unstable_branch}"); + gsp_update_log("Admin {$user_label} updated panel to GitHub Unstable branch {$unstable_branch}"); gsp_log_update_to_db( 'cutting-edge', $unstable_branch, 'success', - 'Updated to cutting-edge branch ' . $unstable_branch . ' by ' . $_SESSION['users_login'], + 'Updated to GitHub Unstable branch ' . $unstable_branch . ' by ' . $_SESSION['users_login'], $result['backup_dir'] ?? null, isset($result['backup_dir']) ? $result['backup_dir'] . '/database.sql' : null, isset($result['backup_dir']) ? $result['backup_dir'] . '/panel-files.tar.gz': null, @@ -1142,10 +1142,10 @@ function gsp_panel_update_section() ); } else { print_failure('Update failed: ' . htmlspecialchars($result['error'])); - gsp_update_log("Admin {$user_label} update to unstable branch {$unstable_branch} FAILED: {$result['error']}"); + gsp_update_log("Admin {$user_label} update to GitHub Unstable branch {$unstable_branch} FAILED: {$result['error']}"); gsp_log_update_to_db( 'cutting-edge', $unstable_branch, 'failed', - 'Update to cutting-edge branch ' . $unstable_branch . ' failed: ' . $result['error'], + 'Update to GitHub Unstable branch ' . $unstable_branch . ' failed: ' . $result['error'], null, null, null, $started_at, $finished_at ); } @@ -1282,32 +1282,34 @@ function gsp_panel_update_section() echo "
\n"; - // ---- Development Version ------------------------------------------------ - echo "

Development Version

\n"; + // ---- GitHub Stable ------------------------------------------------------- + echo "

GitHub Stable

\n"; + echo "

GitHub Stable should always match the latest official numbered release.

\n"; echo "
\n"; echo "\n"; echo "\n"; echo ""; + . "). Continue?\");'>" + . "Update to GitHub Stable"; echo " Branch: " . htmlspecialchars($stable_branch) . "\n"; echo "
\n"; echo "
\n"; - // ---- Cutting Edge Version ----------------------------------------------- - echo "

Cutting Edge Version

\n"; + // ---- GitHub Unstable ----------------------------------------------------- + echo "

GitHub Unstable

\n"; + echo "

GitHub Unstable represents the latest development branch and may be unstable.

\n"; echo "

" - . "⚠ Warning: The cutting edge version may be unstable or contain bugs. Use with caution in production.



\n"; + . "⚠ Warning: GitHub Unstable may contain bugs or incomplete features. Use with caution in production.



\n"; echo "
\n"; echo "\n"; echo "\n"; echo ""; + . " onclick='return confirm(\"WARNING: This is GitHub Unstable and may contain bugs.\\n\\nBack up and update anyway?\");'>" + . "Update to GitHub Unstable"; echo " Branch: " . htmlspecialchars($unstable_branch) . "\n"; echo "
\n"; diff --git a/modules/billing/add_override_price_column.sql b/modules/billing/add_override_price_column.sql new file mode 100644 index 00000000..704a4418 --- /dev/null +++ b/modules/billing/add_override_price_column.sql @@ -0,0 +1,15 @@ +-- Migration: add override_price to billing_service_remote_servers +-- Run once on existing installs that already have the mapping table (db_version 2) +-- but are missing the override_price column (added in db_version 3 / module v3.1). +-- +-- Replace 'gsp_' with your actual table prefix if it differs. +-- +-- This statement is safe to run multiple times only if your MySQL version supports +-- ADD COLUMN IF NOT EXISTS (MySQL 8.0.3+). On older versions, check first: +-- SHOW COLUMNS FROM gsp_billing_service_remote_servers LIKE 'override_price'; + +ALTER TABLE `gsp_billing_service_remote_servers` + ADD COLUMN IF NOT EXISTS `override_price` DECIMAL(10,2) NULL AFTER `enabled`; + +-- If your MySQL is older than 8.0.3, use the conditional form instead: +-- ALTER TABLE `gsp_billing_service_remote_servers` ADD COLUMN `override_price` DECIMAL(10,2) NULL AFTER `enabled`; diff --git a/modules/billing/adminserverlist.php b/modules/billing/adminserverlist.php index c083c1f3..1d0e6011 100644 --- a/modules/billing/adminserverlist.php +++ b/modules/billing/adminserverlist.php @@ -3,389 +3,287 @@ - Admin Server List - GameServers.World + Admin Server / Game Matrix - GameServers.World + Need the XML field reference? "; -echo "Open XML Notes"; -echo ""; - -/* show errors during setup */ -@ini_set('display_errors','1'); -error_reporting(E_ALL); -function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } -function esc_mysqli($db, $v){ return $db->real_escape_string($v); } -function fetch_all_assoc($db, $sql){ - $res = $db->query($sql); - return $res ? $res->fetch_all(MYSQLI_ASSOC) : []; +// Ensure the mapping table exists with the override_price column +$db->query( + "CREATE TABLE IF NOT EXISTS `{$table_prefix}billing_service_remote_servers` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `service_id` INT(11) NOT NULL, + `remote_server_id` INT(11) NOT NULL, + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `override_price` DECIMAL(10,2) NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `svc_rs` (`service_id`, `remote_server_id`), + KEY `service_id` (`service_id`), + KEY `remote_server_id` (`remote_server_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" +); +// Add override_price if this is an older install that already has the table without it +$chk = $db->query("SHOW COLUMNS FROM `{$table_prefix}billing_service_remote_servers` LIKE 'override_price'"); +if ($chk && $chk->num_rows === 0) { + $db->query("ALTER TABLE `{$table_prefix}billing_service_remote_servers` ADD COLUMN `override_price` DECIMAL(10,2) NULL"); } -function col_exists($db, $table, $col){ - $res = $db->query("SHOW COLUMNS FROM `$table` LIKE '".$db->real_escape_string($col)."'"); - return ($res && $res->num_rows > 0); -} -function parse_id_list($s){ - $tokens = preg_split('/\s+/', trim((string)$s)); - $out = []; - foreach ((array)$tokens as $t) { - if ($t === '') continue; - if (preg_match('/^\d+$/', $t)) $out[] = (int)$t; - } - return array_values(array_unique($out)); -} -/* URL helpers for image preview */ -function is_abs_url($u){ return (bool)preg_match('~^(?:https?:)?//|^data:~i', (string)$u); } -function join_base($base, $path){ - $base = rtrim((string)$base, '/'); - $path = ltrim((string)$path, '/'); - return $base !== '' ? $base.'/'.$path : $path; -} - -/* which column holds space-separated locations */ -$locationCol = col_exists($db, "{$table_prefix}billing_services", 'remote_server_id') ? 'remote_server_id' : - (col_exists($db, "{$table_prefix}billing_services", 'remote_server') ? 'remote_server' : 'remote_server_id'); - -/* whether gsp_remote_servers has an 'enabled' column (may be missing on older installs) */ -$rsHasEnabled = col_exists($db, "{$table_prefix}remote_servers", 'enabled'); $flash = []; +$flashType = 'ok'; -/* A) Update global server location enable flags */ -if (isset($_POST['update_remote_servers'])) { - $enabledIds = array_map('intval', $_POST['rs'] ?? []); - $enabledSet = array_flip($enabledIds); - $allIds = fetch_all_assoc($db, "SELECT remote_server_id FROM {$table_prefix}remote_servers"); - foreach ((array)$allIds as $row) { - $id = (int)$row['remote_server_id']; - $e = isset($enabledSet[$id]) ? 1 : 0; - if ($rsHasEnabled) { - $db->query("UPDATE {$table_prefix}remote_servers SET enabled={$e} WHERE remote_server_id={$id}"); +/* ----------------------------------------------------------------------- + SAVE: matrix form submitted +----------------------------------------------------------------------- */ +if (isset($_POST['save_matrix'])) { + $postedServices = $_POST['svc'] ?? []; + $postedMappings = $_POST['map'] ?? []; + + foreach ((array)$postedServices as $sid => $svcData) { + $sid = (int)$sid; + $enabled = isset($svcData['enabled']) ? 1 : 0; + $base_price = number_format((float)($svcData['base_price'] ?? 0), 2, '.', ''); + $period = in_array($svcData['period'] ?? 'monthly', ['daily','monthly','yearly'], true) + ? $svcData['period'] : 'monthly'; + + $price_col = $period === 'daily' ? 'price_daily' : ($period === 'yearly' ? 'price_year' : 'price_monthly'); + $base_esc = $db->real_escape_string($base_price); + $period_esc = $db->real_escape_string($period); + + $db->query( + "UPDATE `{$table_prefix}billing_services` + SET enabled = {$enabled}, + `{$price_col}` = '{$base_esc}' + WHERE service_id = {$sid}" + ); } - } - if ($rsHasEnabled) { - $flash[] = "Server locations updated."; - } else { - $flash[] = "Server locations updated (note: 'enabled' column missing from remote_servers — run add_remote_server_enabled_column.sql migration)."; - } + + // Upsert mappings: for every service x server pair post data received + $allServerIds = []; + $rsRes = $db->query("SELECT remote_server_id FROM `{$table_prefix}remote_servers`"); + while ($rsRes && ($rsRow = $rsRes->fetch_assoc())) { + $allServerIds[] = (int)$rsRow['remote_server_id']; + } + + foreach ((array)$postedServices as $sid => $ignored) { + $sid = (int)$sid; + foreach ($allServerIds as $rid) { + $mapEnabled = isset($postedMappings[$sid][$rid]['enabled']) ? 1 : 0; + $ovRaw = $postedMappings[$sid][$rid]['override_price'] ?? ''; + $override = (trim($ovRaw) === '') ? 'NULL' : "'" . $db->real_escape_string(number_format((float)$ovRaw, 2, '.', '')) . "'"; + + $db->query( + "INSERT INTO `{$table_prefix}billing_service_remote_servers` + (service_id, remote_server_id, enabled, override_price) + VALUES ({$sid}, {$rid}, {$mapEnabled}, {$override}) + ON DUPLICATE KEY UPDATE + enabled = VALUES(enabled), + override_price = VALUES(override_price)" + ); + } + } + + $flash[] = "Matrix saved successfully."; } -/* helper: update one service row from posted array */ -function update_service_row(mysqli $db, string $locationCol, int $sid, array $svc){ - $name = esc_mysqli($db, trim($svc['service_name'] ?? '')); - $priceMonthly = number_format((float)($svc['price_monthly'] ?? 0), 2, '.', ''); - $priceYearly = number_format((float)($svc['price_year'] ?? 0), 2, '.', ''); - $priceDaily = number_format((float)($svc['price_daily'] ?? 0), 2, '.', ''); - $priceMonthEsc = esc_mysqli($db, $priceMonthly); - $priceYearEsc = esc_mysqli($db, $priceYearly); - $priceDailyEsc = esc_mysqli($db, $priceDaily); - $img = esc_mysqli($db, trim($svc['img_url'] ?? '')); - $en = !empty($svc['enabled']) ? 1 : 0; - - $minSlots = max(1, (int)($svc['slot_min_qty'] ?? 1)); - $maxSlots = max($minSlots, (int)($svc['slot_max_qty'] ?? $minSlots)); - - $selected = []; - if (!empty($svc['locations']) && is_array($svc['locations'])) { - $selected = array_map('intval', $svc['locations']); - $selected = array_values(array_unique($selected)); - } - $primary = isset($svc['primary_location']) ? (int)$svc['primary_location'] : 0; - if ($primary && in_array($primary, $selected, true)) { - $selected = array_values(array_diff($selected, [$primary])); - array_unshift($selected, $primary); - } - $locList = implode(' ', $selected); - $locListEsc = esc_mysqli($db, $locList); - - $sql = "UPDATE {$table_prefix}billing_services - SET service_name='{$name}', - `{$locationCol}`='{$locListEsc}', - slot_min_qty={$minSlots}, - slot_max_qty={$maxSlots}, - price_daily='{$priceDailyEsc}', - price_monthly='{$priceMonthEsc}', - price_year='{$priceYearEsc}', - img_url='{$img}', - enabled={$en} - WHERE service_id={$sid}"; - $db->query($sql); -} - -/* B1) PER-ROW UPDATE */ -if (isset($_POST['update_single']) && isset($_POST['service']) && is_array($_POST['service'])) { - $sid = (int)$_POST['update_single']; - if (isset($_POST['service'][$sid])) { - update_service_row($db, $locationCol, $sid, $_POST['service'][$sid]); - $flash[] = "Service #{$sid} updated."; - } -} - -/* B2) BULK UPDATE (single button at bottom) */ -if (isset($_POST['bulk_update']) && !empty($_POST['service']) && is_array($_POST['service'])) { - foreach ((array)$_POST['service'] as $sid => $svc) { - update_service_row($db, $locationCol, (int)$sid, (array)$svc); - } - $flash[] = "All edited services have been updated."; -} - -/* C) Remove a service (separate small form) */ +/* ----------------------------------------------------------------------- + Remove a service +----------------------------------------------------------------------- */ if (isset($_POST['remove_service'], $_POST['service_id_remove'])) { - $sid = (int)$_POST['service_id_remove']; - $db->query("DELETE FROM {$table_prefix}billing_services WHERE service_id={$sid}"); - $flash[] = "Service #{$sid} removed."; + $sid = (int)$_POST['service_id_remove']; + $db->query("DELETE FROM `{$table_prefix}billing_service_remote_servers` WHERE service_id = {$sid}"); + $db->query("DELETE FROM `{$table_prefix}billing_services` WHERE service_id = {$sid}"); + $flash[] = "Service #{$sid} removed."; } -/* fetch data for UI */ -// Build remote-servers query — include `enabled` only when the column exists (older installs may be missing it). -if ($rsHasEnabled) { - $remoteServers = fetch_all_assoc($db, "SELECT remote_server_id, remote_server_name, enabled FROM {$table_prefix}remote_servers ORDER BY remote_server_name"); -} else { - $remoteServers = fetch_all_assoc($db, "SELECT remote_server_id, remote_server_name, 1 AS enabled FROM {$table_prefix}remote_servers ORDER BY remote_server_name"); +/* ----------------------------------------------------------------------- + Load data +----------------------------------------------------------------------- */ +$remoteServers = []; +$rsRes = $db->query("SELECT remote_server_id, remote_server_name FROM `{$table_prefix}remote_servers` ORDER BY remote_server_name"); +while ($rsRes && ($row = $rsRes->fetch_assoc())) { + $remoteServers[] = $row; +} + +$services = []; +$svcRes = $db->query( + "SELECT service_id, service_name, enabled, price_daily, price_monthly, price_year + FROM `{$table_prefix}billing_services` + ORDER BY service_name" +); +while ($svcRes && ($row = $svcRes->fetch_assoc())) { + $services[] = $row; +} + +// Load existing mappings into a lookup: $mappings[$service_id][$remote_server_id] = ['enabled'=>..,'override_price'=>..] +$mappings = []; +$mapRes = $db->query( + "SELECT service_id, remote_server_id, enabled, override_price + FROM `{$table_prefix}billing_service_remote_servers`" +); +while ($mapRes && ($row = $mapRes->fetch_assoc())) { + $mappings[(int)$row['service_id']][(int)$row['remote_server_id']] = [ + 'enabled' => (int)$row['enabled'], + 'override_price' => $row['override_price'], + ]; } -$services = fetch_all_assoc($db, "SELECT service_id, service_name, `{$locationCol}` AS locs, slot_min_qty, slot_max_qty, price_daily, price_monthly, price_year, img_url, enabled FROM {$table_prefix}billing_services ORDER BY service_name"); ?> - -
".h($m)."
"; ?> - + +
+ - -
- ⚠ Schema notice: The table is missing the enabled column. - Server location enable/disable is currently non-functional. - Run modules/billing/add_remote_server_enabled_column.sql to add the column. -
- +

Game × Server Matrix

+

+ Enable or disable each game for billing, set its base price and billing period, then + toggle availability per server and optionally override the price for that location. + Leave override blank to use the base price. +

-

Enable/Disable Server Locations (Global)

-
- -
- - - -
-
-
- -
- -

Current Services

- -

No services found.

+ +

No billing services found. Add services first via the database or the panel.

-
+ - +
+
- - - - - - - - - - + + + + + + + - - - - + 0) { + $basePrice = number_format((float)$svc['price_monthly'], 2, '.', ''); + $period = 'monthly'; + } elseif ((float)$svc['price_daily'] > 0) { + $basePrice = number_format((float)$svc['price_daily'], 2, '.', ''); + $period = 'daily'; + } elseif ((float)$svc['price_year'] > 0) { + $basePrice = number_format((float)$svc['price_year'], 2, '.', ''); + $period = 'yearly'; + } else { + $basePrice = '0.00'; + $period = 'monthly'; + } + ?> - - - - - - - - - - - - - - - - - + 0, 'override_price' => null]; + $mapEnabled = (int)$mapEntry['enabled']; + $ovPrice = $mapEntry['override_price']; + ?> + + - - - - - -
EnabledService Name (ID below)Min SlotsMax SlotsPrice (Daily)Price (Monthly)Price (Year)Thumbnail URLPreviewUpdate RowGameEnabledBase Price ($)Period
+ # +
- - > - - -
ID:
+
+ +
ID:
- + + > - + - + - - - - - - - - preview - - (no image) - - - - + + > +
+ +
-
- - - - -
-
- -
-
+
+ +
+
+ +

Remove a Service

+
+ + +
-

Remove a Service

-
- - - -
-

Environment

- - - - - - -
Site Base URL
Data directory
PHP SAPI
Writable?
XML ReferenceOpen XML Notes
+

Legend: Checkbox = server is available for this game. + Override price = customer pays this amount instead of the base price for that location. + Leave override blank to use the game base price.

+

+ Availability is controlled entirely by . + No entry or enabled = 0 means the server is not offered for that game. +

- - - - + diff --git a/modules/billing/module.php b/modules/billing/module.php index 291a063d..71ff12f0 100644 --- a/modules/billing/module.php +++ b/modules/billing/module.php @@ -24,8 +24,8 @@ // Module general information $module_title = "billing"; -$module_version = "3.0"; -$db_version = 2; +$module_version = "3.1"; +$db_version = 3; $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."; @@ -173,4 +173,9 @@ $install_queries[1] = array( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;" ); +// Version 3: Add override_price to service-to-server mapping table +$install_queries[2] = array( + "ALTER TABLE `".OGP_DB_PREFIX."billing_service_remote_servers` ADD COLUMN `override_price` DECIMAL(10,2) NULL AFTER `enabled`" +); + ?> diff --git a/modules/billing/order.php b/modules/billing/order.php index 45a11270..8f2c7933 100644 --- a/modules/billing/order.php +++ b/modules/billing/order.php @@ -188,46 +188,27 @@ if ($row['price_monthly'] == 0.0) { Location - query($query); - foreach ((array)$result as $rs) - { - - $rsID =$rs['remote_server_id']; - $rsNAME = $rs['remote_server_name']; - //echo ""; - // add disabled to lable and input if $rsID is in out_of_stock - $is_unavailable = ""; - $service_text_color = ""; - - - if($rs['enabled']==0) - { - $is_unavailable = "disabled"; - $service_text_color = "red"; - } - if($is_unavailable == "") - { - $available_server = true; - } - - - //default radio button - // // - echo "
- - -
"; + $sid_order = (int)$row['service_id']; + $mappedQuery = "SELECT m.remote_server_id, m.override_price, r.remote_server_name + FROM {$table_prefix}billing_service_remote_servers m + JOIN {$table_prefix}remote_servers r + ON r.remote_server_id = m.remote_server_id + WHERE m.service_id = {$sid_order} AND m.enabled = 1 + ORDER BY r.remote_server_name"; + $mappedResult = $db->query($mappedQuery); + if ($mappedResult) { + while ($rs = $mappedResult->fetch_assoc()) { + $rsID = (int)$rs['remote_server_id']; + $rsNAME = htmlspecialchars((string)$rs['remote_server_name'], ENT_QUOTES, 'UTF-8'); + $available_server = true; + echo "
\n" + . " \n" + . " \n" + . "
\n"; } } ?>