Panel/modules/gamemanager/home_handling_functions.php
copilot-swe-agent[bot] 2f62bd32c9
fix: standardize billing order status values and fix expiration lookup
- billing_integration.php: admin-created servers now use status='Active' (was 'installed')
- home_handling_functions.php: expiration query uses status IN ('Active','Invoiced') only
- my_account.php: renewable_statuses includes canonical 'active'/'invoiced'; legacy labels updated
- admin_orders.php: add orphaned home_id diagnostics section
- normalize_billing_order_status.sql: new migration to convert installed/paid→Active, suspended→Invoiced

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/c56f04bb-ecce-4f1b-9bbd-c5f83107da1d

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
2026-05-05 15:55:39 +00:00

580 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
*
* OGP - Open Game Panel
* Copyright (C) 2008 - 2018 The OGP Development Team
*
* http://www.opengamepanel.org/
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
function get_query_port($server_xml, $server_port) {
if ($server_xml->query_port) {
if ($server_xml->query_port['type'] == 'add') {
return $server_port + $server_xml->query_port;
}
if ($server_xml->query_port['type'] == 'subtract') {
return $server_port - $server_xml->query_port;
}
}
return $server_port;
}
function get_start_cmd($remote,$server_xml,$home_info,$mod_id,$ip,$port,$db)
{
$last_param = json_decode($home_info['last_param'], True);
$os = $remote->what_os();
$isAdmin = false;
if(hasValue($_SESSION) && hasValue($_SESSION['user_id'])){
$isAdmin = $db->isAdmin($_SESSION['user_id']);
}
$cli_param_data['GAME_TYPE'] = $home_info['mods'][$mod_id]['mod_key'];
$cli_param_data['IP'] = $ip;
$cli_param_data['PORT'] = $port;
$cli_param_data['HOSTNAME'] = $home_info['home_name'];
$cli_param_data['PID_FILE'] = "ogp_game_startup.pid";
// Linux
if( preg_match("/Linux/", $os) )
{
if(preg_match("/_win(32|64)?$/", $home_info['game_key']))
{
$home_path_wine = $remote->exec("winepath -w ".$home_info['home_path']);
$home_path_wine = str_replace("\\","\\\\", $home_path_wine);
$home_path_wine = trim($home_path_wine);
$cli_param_data['BASE_PATH'] = $home_path_wine;
$cli_param_data['HOME_PATH'] = $home_path_wine;
$cli_param_data['SAVE_PATH'] = $home_path_wine;
$cli_param_data['OUTPUT_PATH'] = $home_path_wine;
$cli_param_data['USER_PATH'] = $home_path_wine;
}
else
{
$cli_param_data['BASE_PATH'] = $home_info['home_path'];
$cli_param_data['HOME_PATH'] = $home_info['home_path'];
$cli_param_data['SAVE_PATH'] = $home_info['home_path'];
$cli_param_data['OUTPUT_PATH'] = $home_info['home_path'];
$cli_param_data['USER_PATH'] = $home_info['home_path'];
}
}
// Windows
elseif( preg_match("/CYGWIN/", $os) )
{
$home_path_win = $remote->exec("cygpath -w ".$home_info['home_path']);
$home_path_win = str_replace("\\","\\\\", $home_path_win);
$home_path_win = trim($home_path_win);
$cli_param_data['BASE_PATH'] = $home_path_win;
$cli_param_data['HOME_PATH'] = $home_path_win;
$cli_param_data['SAVE_PATH'] = $home_path_win;
$cli_param_data['OUTPUT_PATH'] = $home_path_win;
$cli_param_data['USER_PATH'] = $home_path_win;
}
if ($server_xml->protocol == "gameq")
{
$cli_param_data['QUERY_PORT'] = get_query_port ($server_xml, $port);
}
elseif ($server_xml->protocol == "lgsl")
{
require('protocol/lgsl/lgsl_protocol.php');
$get_ports = lgsl_port_conversion((string)$server_xml->lgsl_query_name, $port, "", "");
$cli_param_data['QUERY_PORT'] = $get_ports['1'];
}
elseif ($server_xml->protocol == "teamspeak3")
{
$cli_param_data['QUERY_PORT'] = "10011";
}
$cli_param_data['MAP'] = ($last_param === NULL or !isset($last_param['map'])) ? "" : $last_param['map'];
$cli_param_data['PLAYERS'] = ($last_param === NULL or !isset($last_param['players'])) ?
isset($home_info['mods'][$mod_id]['max_players']) ?
$home_info['mods'][$mod_id]['max_players'] : "1" : $last_param['players'];
$cli_param_data['CONTROL_PASSWORD'] = $home_info['control_password'];
$start_cmd = "";
// If the template is empty then these are not needed.
if ( $server_xml->cli_template )
{
$start_cmd = $server_xml->cli_template;
if ( $server_xml->cli_params )
{
foreach ( $server_xml->cli_params->cli_param as $cli )
{
// If s is found the param is seperated with space
$add_space = preg_match( "/s/", $cli['options'] ) > 0 ? " " : "";
$cli_value = $cli_param_data[(string) $cli['id'] ];
// If q is found we add quotes around the value.
if ( preg_match( "/q/", $cli['options'] ) > 0 )
{
$cli_value = "\"".$cli_value."\"";
}
$start_cmd = preg_replace( "/%".$cli['id']."%/",
$cli['cli_string'].$add_space.$cli_value, $start_cmd );
}
}
if ( $server_xml->reserve_ports )
{
foreach ( $server_xml->reserve_ports->port as $reserve_port )
{
// If s is found the param is seperated with space
$add_space = preg_match( "/s/", $reserve_port['options'] ) > 0 ? " " : "";
$cli_value = $reserve_port['type'] == "add" ? $port + (string) $reserve_port:
$port - (string) $reserve_port;
// If q is found we add quotes around the value.
if ( preg_match( "/q/", $reserve_port['options'] ) > 0 )
{
$cli_value = "\"".$cli_value."\"";
}
$start_cmd = preg_replace( "/%".$reserve_port['id']."%/",
$reserve_port['cli_string'].$add_space.$cli_value, $start_cmd );
}
}
}
if ( $isAdmin )
{
$home_info['access_rights'] = "ufpet";
}
$param_access_enabled = preg_match("/p/",$home_info['access_rights']) > 0 ? TRUE : FALSE;
if ($param_access_enabled && $last_param !== NULL and isset($server_xml->server_params->param) )
{
foreach($server_xml->server_params->param as $param)
{
foreach ((array)$last_param as $paramKey => $paramValue)
{
if (!isset($paramValue))
$paramValue = (string)$param->default;
if ($param['key'] == $paramKey)
{
if (0 == strlen($paramValue))
continue;
if ($param['key'] == $paramValue) // it's a checkbox
$new_param = $paramKey;
elseif($param->option == "ns" or $param->options == "ns")
$new_param = $paramKey.clean_server_param_value($paramValue, $server_xml->cli_allow_chars);
elseif($param->option == "q" or $param->options == "q")
$new_param = $paramKey . '"' . clean_server_param_value($paramValue, $server_xml->cli_allow_chars) . '"';
elseif($param->option == "s" or $param->options == "s")
$new_param = $paramKey . ' ' . clean_server_param_value($paramValue, $server_xml->cli_allow_chars);
else
$new_param = $paramKey . ' "' . clean_server_param_value($paramValue, $server_xml->cli_allow_chars) . '"';
if ($param['id'] == NULL || $param['id'] == "")
$start_cmd .= ' '.$new_param;
else
$start_cmd = preg_replace( "/%".$param['id']."%/", $new_param, $start_cmd );
}
}
if ($param['id'] != NULL && $param['id'] != ""){
$start_cmd = preg_replace( "/%".$param['id']."%/", '', $start_cmd );
}
}
}
$extra_param_access_enabled = preg_match("/e/",$home_info['access_rights']) > 0 ? TRUE:FALSE;
if ( array_key_exists('extra', (array)$last_param) && $extra_param_access_enabled )
$extra_default = $last_param['extra'];
else
$extra_default = $home_info['mods'][$mod_id]['extra_params'];
$start_cmd .= " ".str_replace("\\\\", "\\", clean_server_param_value($extra_default, $server_xml->cli_allow_chars));
return $start_cmd;
}
// This function is used to batch stop/restart servers in background.
function exec_operation( $action, $home_id, $mod_id, $ip, $port, $override = false )
{
if(!is_numeric($port))
return FALSE;
if(!preg_match("/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/",$ip))
return FALSE;
global $db;
$isAdmin = false;
if(hasValue($_SESSION) && hasValue($_SESSION['user_id'])){
$isAdmin = $db->isAdmin($_SESSION['user_id']);
}
if($override || $isAdmin)
$home_info = $db->getGameHome($home_id);
else
$home_info = $db->getUserGameHome($_SESSION['user_id'],$home_id);
if( $home_info === FALSE )
return FALSE;
$server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$home_info['home_cfg_file']);
if ( !$server_xml )
return FALSE;
require_once('includes/lib_remote.php');
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
$os = $remote->what_os();
if ( $action != "stop" )
{
if( $server_xml->replace_texts )
{
$server_home = $home_info;
if( isset($server_xml->lgsl_query_name) )
require_once('protocol/lgsl/lgsl_protocol.php');
require_once("modules/gamemanager/cfg_text_replace.php");
}
}
$screen_running = $remote->is_screen_running(OGP_SCREEN_TYPE_HOME,$home_info['home_id']) === 1;
if ( $action == "stop" AND $screen_running )
{
$remote_retval = $remote->remote_stop_server($home_info['home_id'],
$ip, $port, $server_xml->control_protocol,
$home_info['control_password'],$server_xml->control_protocol_type, $home_info['home_path']);
$db->logger(get_lang_f('server_stopped', $home_info['home_name'] ) . "($ip:$port)");
if ( $remote_retval === -1 )
return FALSE;
elseif( $remote_retval === -2 )
return FALSE;
else
{
$firewall_settings = $db->getFirewallSettings($home_info['remote_server_id']);
if ($firewall_settings['status'] == "enable")
{
$ip_ports = $db->getHomeIpPorts($home_info['home_id']);
if (!is_array($ip_ports)) {
$ip_ports = [];
}
foreach ((array)$ip_ports as $ip_port)
{
if ($server_xml->protocol == "gameq")
{
$query_port = get_query_port($server_xml, $ip_port['port']);
}
elseif ($server_xml->protocol == "lgsl")
{
require('protocol/lgsl/lgsl_protocol.php');
$get_ports = lgsl_port_conversion((string)$server_xml->lgsl_query_name, $ip_port['port'], "", "");
$query_port = $get_ports['1'];
}
elseif ($server_xml->protocol == "teamspeak3")
{
$query_port = "10011";
}
set_firewall($remote, $firewall_settings, 'deny', $ip_port['port'], $ip_port['ip']);
if(isset($query_port) and $query_port != "" and $query_port != $ip_port['port'])
set_firewall($remote, $firewall_settings, 'deny', $query_port, $ip_port['ip']);
}
}
return TRUE;
}
}
elseif ( $action == "restart" AND $screen_running )
{
$start_cmd = get_start_cmd($remote,$server_xml,$home_info,$mod_id,$ip,$port,$db);
// Do text replacements in cfg file
if( $server_xml->replace_texts )
{
if (is_array($home_info['mods'][$mod_id])) {
foreach($home_info['mods'][$mod_id] as $key => $value)
{
$home_info[$key] = $value;
}
}
$server_home = $home_info;
if( isset($server_xml->lgsl_query_name) )
require_once('protocol/lgsl/lgsl_protocol.php');
require_once("modules/gamemanager/cfg_text_replace.php");
}
// Run pre-start commands
if(isset($server_xml->pre_start) && !empty($server_xml->pre_start)){
$preStart = trim($server_xml->pre_start);
}else{
$preStart = "";
}
// Environment variables
if(isset($server_xml->environment_variables) && !empty($server_xml->environment_variables)){
$envVars = trim($server_xml->environment_variables);
}else{
$envVars = "";
}
// Additional files to lock
if(isset($server_xml->lock_files) && !empty($server_xml->lock_files)){
$lockFiles = trim($server_xml->lock_files);
}else{
$lockFiles = "";
}
if(!empty($lockFiles)){
// Linux only call
if(preg_match("/Linux/", $os)){
$lockedFilesStatus = $remote->lock_additional_home_files($home_info['home_path'], $lockFiles, "lock");
}
}
$remote_retval = $remote->remote_restart_server($home_info['home_id'],$ip,$port,$server_xml->control_protocol,
$home_info['control_password'],$server_xml->control_protocol_type,$home_info['home_path'],
$server_xml->server_exec_name,$server_xml->exe_location,$start_cmd,
$home_info['mods'][$mod_id]['cpu_affinity'],
$home_info['mods'][$mod_id]['nice'],
$preStart,
$envVars,
$server_xml->game_key,
(isset( $server_xml->console_log ) ? $server_xml->console_log : ""));
$db->logger(get_lang_f('server_restarted', $home_info['home_name']) . "($ip:$port)");
if ( $remote_retval === -1 )
return FALSE;
else if ( $remote_retval === -2 )
return FALSE;
else
{
$ip_id = $db->getIpIdByIp($ip);
$db->delServerStatusCache($ip_id,$port);
}
}
elseif ( $action == "start" AND ! $screen_running )
{
$start_cmd = get_start_cmd($remote,$server_xml,$home_info,$mod_id,$ip,$port,$db);
// Do text replacements in cfg file
if( $server_xml->replace_texts )
{
if (is_array($home_info['mods'][$mod_id])) {
foreach($home_info['mods'][$mod_id] as $key => $value)
{
$home_info[$key] = $value;
}
}
$server_home = $home_info;
if( isset($server_xml->lgsl_query_name) )
require_once('protocol/lgsl/lgsl_protocol.php');
require_once("modules/gamemanager/cfg_text_replace.php");
}
// Run pre-start commands
if(isset($server_xml->pre_start) && !empty($server_xml->pre_start)){
$preStart = trim($server_xml->pre_start);
}else{
$preStart = "";
}
// Environment variables
if(isset($server_xml->environment_variables) && !empty($server_xml->environment_variables)){
$envVars = trim($server_xml->environment_variables);
}else{
$envVars = "";
}
// Additional files to lock
if(isset($server_xml->lock_files) && !empty($server_xml->lock_files)){
$lockFiles = trim($server_xml->lock_files);
}else{
$lockFiles = "";
}
if(!empty($lockFiles)){
// Linux only call
if(preg_match("/Linux/", $os)){
$lockedFilesStatus = $remote->lock_additional_home_files($home_info['home_path'], $lockFiles, "lock");
}
}
$start_retval = $remote->universal_start($home_info['home_id'],
$home_info['home_path'],
$server_xml->server_exec_name,
$server_xml->exe_location,
$start_cmd, $port, $ip,
$home_info['mods'][$mod_id]['cpu_affinity'],
$home_info['mods'][$mod_id]['nice'],
$preStart,
$envVars,
$server_xml->game_key,
(isset( $server_xml->console_log ) ? $server_xml->console_log : "")
);
$db->logger(get_lang('server_started') . " (".$home_info['home_name']." $ip:$port)");
if( $start_retval == AGENT_ERROR_NOT_EXECUTABLE or $start_retval <= 0)
return FALSE;
else
{
$firewall_settings = $db->getFirewallSettings($home_info['remote_server_id']);
if ($firewall_settings['status'] == "enable")
{
$ip_ports = $db->getHomeIpPorts($home_info['home_id']);
if (!is_array($ip_ports)) {
$ip_ports = [];
}
foreach ((array)$ip_ports as $ip_port)
{
if ($server_xml->protocol == "gameq")
{
$query_port = get_query_port($server_xml, $ip_port['port']);
}
elseif ($server_xml->protocol == "lgsl")
{
require('protocol/lgsl/lgsl_protocol.php');
$get_ports = lgsl_port_conversion((string)$server_xml->lgsl_query_name, $ip_port['port'], "", "");
$query_port = $get_ports['1'];
}
elseif ($server_xml->protocol == "teamspeak3")
{
$query_port = "10011";
}
set_firewall($remote, $firewall_settings, 'allow', $ip_port['port'], $ip_port['ip']);
if(isset($query_port) and $query_port != "" and $query_port != $ip_port['port'])
set_firewall($remote, $firewall_settings, 'allow', $query_port, $ip_port['ip']);
}
}
$ip_id = $db->getIpIdByIp($ip);
$db->delServerStatusCache($ip_id,$port);
return TRUE;
}
}
}
function get_monitor_buttons($server_home, $server_xml)
{
global $db;
$buttons = array();
$installed_modules = $db->getInstalledModules();
if (!is_array($installed_modules)) {
$installed_modules = [];
}
foreach ((array)$installed_modules as $installed_module)
{
$buttons_file = "modules/$installed_module[folder]/monitor_buttons.php";
if(file_exists($buttons_file))
{
require($buttons_file);
if (is_array($module_buttons)) {
$buttons = array_merge($buttons, $module_buttons);
}
unset($module_buttons);
}
}
$buttons_html = "";
if (is_array($buttons)) {
foreach ((array)$buttons as $button)
$buttons_html .= $button."\n";
}
return $buttons_html;
}
/**
* Returns an HTML-formatted expiration label for the given server home_id.
*
* Source of truth: billing_orders.end_date (DATETIME, NULL means no date set).
* billing_orders.home_id is the direct FK to server_homes.home_id.
* Only rows where home_id != '0' are considered (home_id = '0' means not yet
* provisioned).
*
* Color thresholds:
* green more than 10 days remaining (shows actual date)
* yellow 410 days remaining (shows "X days remaining")
* red 13 days remaining (shows "X days remaining")
* red less than 1 day but not yet expired (shows "Less than 1 day remaining")
* red already expired/suspended (shows "Suspended")
* red no order or NULL/invalid end_date (shows "No expiration date found")
*
* @param int $home_id The server home ID.
* @return string Safe HTML string ready for inline display.
*/
function get_server_billing_expiration_html(int $home_id): string
{
global $db;
// Query billing_orders directly by home_id — the canonical FK relationship.
// billing_orders.home_id is VARCHAR(255), so we quote the value in the query.
// We exclude rows where home_id = '0' (not yet provisioned).
//
// Only canonical active statuses are considered:
// 'Active' provisioned and current (set by payment capture or admin creation)
// 'Invoiced' renewal invoice generated; service still running while unpaid
//
// Terminal statuses ('Expired', 'in-cart', 'cancelled', 'refunded') and legacy
// statuses ('installed', 'paid', 'suspended') are not matched here.
// Run normalize_billing_order_status.sql to migrate any legacy rows first.
//
// OGP_DB_PREFIX is replaced at runtime by the panel DB wrapper (str_replace).
$rows = $db->resultQuery(
"SELECT end_date
FROM OGP_DB_PREFIXbilling_orders
WHERE home_id = '" . intval($home_id) . "'
AND home_id != '0'
AND status IN ('Active','Invoiced')
ORDER BY end_date DESC
LIMIT 1"
);
// resultQuery returns FALSE both on error and when 0 rows are found.
// Either way, there is no billing order for this server.
if ($rows === false) {
return "<span style='color:red;'>No expiration date found</span>";
}
// end_date may be NULL (e.g. order created but payment not yet captured).
if (empty($rows[0]['end_date'])) {
return "<span style='color:red;'>No expiration date found</span>";
}
// Parse end_date using DateTime for PHP 8.3+ compatibility.
try {
$end_dt = new DateTime($rows[0]['end_date']);
} catch (\Exception $e) {
return "<span style='color:red;'>No expiration date found</span>";
}
$now = new DateTime();
$diff = $now->diff($end_dt);
if ($end_dt <= $now) {
// Server billing period has already passed — treat as suspended.
return "<span style='color:red;'>Suspended</span>";
}
// $diff->days is the total number of whole days between $now and $end_dt.
$days_remaining = (int)$diff->days;
$display_date = htmlentities($end_dt->format('Y-m-d H:i'), ENT_QUOTES, 'UTF-8');
if ($days_remaining > 10) {
// More than 10 days: show the actual expiration date in green.
return "<span style='color:green;'>" . $display_date . "</span>";
} elseif ($days_remaining >= 4) {
// 410 days: yellow warning (darkgoldenrod for WCAG contrast).
return "<span style='color:#B8860B;'>" . htmlentities($days_remaining, ENT_QUOTES, 'UTF-8') . " days remaining</span>";
} elseif ($days_remaining >= 1) {
// 13 days: urgent red warning.
return "<span style='color:red;'>" . htmlentities($days_remaining, ENT_QUOTES, 'UTF-8') . " days remaining</span>";
} else {
// Less than one full day but not yet expired.
return "<span style='color:red;'>Less than 1 day remaining</span>";
}
}
?>