Merge pull request #130 from GameServerPanel/copilot/gsp-improve-billing-website-config
This commit is contained in:
commit
0f4c4b3634
6 changed files with 728 additions and 139 deletions
|
|
@ -3,32 +3,9 @@
|
|||
require_once(__DIR__ . '/includes/admin_auth.php');
|
||||
require_once(__DIR__ . '/includes/config_loader.php');
|
||||
|
||||
// Ensure site variables are defined regardless of which config was loaded.
|
||||
// The panel config (loaded first by config_loader) does not define these billing-specific
|
||||
// variables. Try loading them from the billing config.inc.php if not already set.
|
||||
if (!isset($SITE_BASE_URL) || !isset($SITE_DATA_DIR)) {
|
||||
$billingLocalCfg = __DIR__ . '/includes/config.inc.php';
|
||||
if (is_readable($billingLocalCfg) && defined('BILLING_CONFIG_PATH') && BILLING_CONFIG_PATH !== $billingLocalCfg) {
|
||||
// Panel config was loaded; read billing config vars without re-running DB setup.
|
||||
// Use a temporary scope to avoid overwriting DB credentials.
|
||||
$__billing_cfg_vars = (static function() use ($billingLocalCfg) {
|
||||
$SITE_BASE_URL = '';
|
||||
$SITE_DATA_DIR = '';
|
||||
@include $billingLocalCfg;
|
||||
return ['base' => $SITE_BASE_URL ?? '', 'data' => $SITE_DATA_DIR ?? ''];
|
||||
})();
|
||||
if (!isset($SITE_BASE_URL)) $SITE_BASE_URL = $__billing_cfg_vars['base'];
|
||||
if (!isset($SITE_DATA_DIR)) $SITE_DATA_DIR = $__billing_cfg_vars['data'];
|
||||
unset($__billing_cfg_vars, $billingLocalCfg);
|
||||
}
|
||||
}
|
||||
// Final safe defaults if still not set.
|
||||
if (!isset($SITE_BASE_URL)) {
|
||||
$SITE_BASE_URL = '';
|
||||
}
|
||||
if (!isset($SITE_DATA_DIR)) {
|
||||
$SITE_DATA_DIR = realpath(__DIR__ . '/data') ?: (__DIR__ . '/data');
|
||||
}
|
||||
// config_loader.php now always loads billing/includes/config.inc.php first (which contains
|
||||
// SITE_BASE_URL, SITE_DATA_DIR, PayPal settings, etc.) and then overlays panel DB settings
|
||||
// when inside a GSP panel tree. Safe defaults are applied by the loader for any missing vars.
|
||||
|
||||
include(__DIR__ . '/includes/top.php');
|
||||
include(__DIR__ . '/includes/menu.php');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
<?php
|
||||
// Admin config editor — lightweight editor for _website/includes/config.inc.php
|
||||
/**
|
||||
* Admin Config Editor
|
||||
*
|
||||
* Provides two ways to edit modules/billing/includes/config.inc.php:
|
||||
* A. Interactive form (top) — fields for each billing-specific setting.
|
||||
* B. Raw PHP editor (bottom) — direct file content textarea (advanced).
|
||||
*
|
||||
* Both methods create a timestamped backup before saving and apply the
|
||||
* $SITE_CONFIG_BACKUP_RETENTION limit (default 5) after writing.
|
||||
* A post-save php -l syntax check rolls back the file on parse errors.
|
||||
*
|
||||
* Database settings (db_host, db_port, db_user, db_pass, db_name, table_prefix)
|
||||
* are shown as read-only when the module is installed inside a GSP panel tree.
|
||||
* They are managed via the panel and synced automatically by config_loader.php.
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/includes/admin_auth.php');
|
||||
require_once(__DIR__ . '/includes/config_loader.php');
|
||||
include(__DIR__ . '/includes/top.php');
|
||||
|
|
@ -9,75 +24,291 @@ if (session_status() === PHP_SESSION_NONE) {
|
|||
session_name('opengamepanel_web');
|
||||
session_start();
|
||||
}
|
||||
if (empty($_SESSION['admin_csrf'])) $_SESSION['admin_csrf'] = bin2hex(random_bytes(16));
|
||||
if (empty($_SESSION['admin_csrf'])) {
|
||||
$_SESSION['admin_csrf'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
$csrf = $_SESSION['admin_csrf'];
|
||||
|
||||
$cfgPath = __DIR__ . '/includes/config.inc.php';
|
||||
$bakDir = dirname($cfgPath) . '/backups';
|
||||
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
|
||||
function h(string $s): string
|
||||
{
|
||||
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: apply backup retention — delete oldest .bak files beyond the limit.
|
||||
// Only touches files with names matching *.bak inside the expected backup dir.
|
||||
// ---------------------------------------------------------------------------
|
||||
function billing_admin_apply_retention(string $dir, int $retention): void
|
||||
{
|
||||
$retention = max(1, min(10, $retention));
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
$files = glob($dir . '/*.bak');
|
||||
if (!is_array($files) || count($files) <= $retention) {
|
||||
return;
|
||||
}
|
||||
// Sort oldest first (by file modification time)
|
||||
usort($files, static function (string $a, string $b): int {
|
||||
return filemtime($a) <=> filemtime($b);
|
||||
});
|
||||
$toDelete = count($files) - $retention;
|
||||
for ($i = 0; $i < $toDelete; $i++) {
|
||||
@unlink($files[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create a backup of the config file; returns backup filename or ''.
|
||||
// ---------------------------------------------------------------------------
|
||||
function billing_admin_create_backup(string $cfgPath, string $bakDir): string
|
||||
{
|
||||
@mkdir($bakDir, 0775, true);
|
||||
$bakName = $bakDir . '/config.inc.php.' . date('Ymd-His') . '.' . bin2hex(random_bytes(4)) . '.bak';
|
||||
if (!copy($cfgPath, $bakName)) {
|
||||
return '';
|
||||
}
|
||||
return $bakName;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run php -l on a file and return [ok, output].
|
||||
// ---------------------------------------------------------------------------
|
||||
function billing_admin_lint(string $filePath): array
|
||||
{
|
||||
$phpExec = PHP_BINARY ?: null;
|
||||
if (!$phpExec) {
|
||||
return [true, 'PHP executable not found; skipping syntax check.'];
|
||||
}
|
||||
$cmd = escapeshellarg($phpExec) . ' -l ' . escapeshellarg($filePath);
|
||||
$out = [];
|
||||
$rc = 0;
|
||||
@exec($cmd . ' 2>&1', $out, $rc);
|
||||
return [$rc === 0, implode("\n", $out)];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: generate canonical config.inc.php content from an array of values.
|
||||
// DB settings are preserved from the existing file; only billing fields change.
|
||||
// ---------------------------------------------------------------------------
|
||||
function billing_admin_build_config(string $existingContent, array $vals): string
|
||||
{
|
||||
// Extract current DB settings from existing file content so we never lose them.
|
||||
$dbLines = [];
|
||||
foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'] as $var) {
|
||||
if (preg_match('/^\s*\$' . preg_quote($var, '/') . '\s*=.*$/m', $existingContent, $m)) {
|
||||
$dbLines[$var] = rtrim($m[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$q = static function (string $v): string {
|
||||
return '"' . addslashes($v) . '"';
|
||||
};
|
||||
|
||||
$sandbox = (bool)$vals['paypal_sandbox'];
|
||||
$retention = max(1, min(10, (int)($vals['backup_retention'] ?? 5)));
|
||||
$baseUrl = trim($vals['SITE_BASE_URL'] ?? '');
|
||||
$bg = trim($vals['SITE_BACKGROUND'] ?? 'images/dark.jpg');
|
||||
$dataDir = trim($vals['SITE_DATA_DIR'] ?? '');
|
||||
|
||||
$dbBlock = '';
|
||||
foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'] as $var) {
|
||||
if (isset($dbLines[$var])) {
|
||||
$dbBlock .= $dbLines[$var] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$dataDirLine = ($dataDir !== '' && $dataDir !== 'auto')
|
||||
? '$SITE_DATA_DIR = ' . $q($dataDir) . ';'
|
||||
: "\$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';";
|
||||
|
||||
return '<?php' . "\n"
|
||||
. '###############################################' . "\n"
|
||||
. '# Website Database Configuration' . "\n"
|
||||
. '# This file contains the database connection' . "\n"
|
||||
. '# settings for the billing website.' . "\n"
|
||||
. '#' . "\n"
|
||||
. '# Managed via Admin > Edit Config.' . "\n"
|
||||
. '###############################################' . "\n"
|
||||
. $dbBlock
|
||||
. "\n"
|
||||
. '// Optional: base URL used by admin pages to build absolute image previews.' . "\n"
|
||||
. '// Leave empty to prefer relative paths (local folder).' . "\n"
|
||||
. '$SITE_BASE_URL = ' . $q($baseUrl) . ';' . "\n"
|
||||
. "\n"
|
||||
. '// Normalize: ensure either empty or ends without trailing slash' . "\n"
|
||||
. '$SITE_BASE_URL = trim((string)$SITE_BASE_URL);' . "\n"
|
||||
. "\n"
|
||||
. '// Site-wide background image (relative to site root).' . "\n"
|
||||
. '$SITE_BACKGROUND = ' . $q($bg) . ';' . "\n"
|
||||
. '// Normalize' . "\n"
|
||||
. '$SITE_BACKGROUND = trim((string)$SITE_BACKGROUND);' . "\n"
|
||||
. "\n"
|
||||
. '// Data directory for persisted payment webhook JSON files (relative to repo root)' . "\n"
|
||||
. $dataDirLine . "\n"
|
||||
. "\n"
|
||||
. '// PayPal configuration — set credentials here, never in API files' . "\n"
|
||||
. '$paypal_sandbox = ' . ($sandbox ? 'true' : 'false') . '; // Set to false for live payments' . "\n"
|
||||
. '$paypal_client_id = ' . $q($vals['paypal_client_id'] ?? '') . '; // Your PayPal Client ID' . "\n"
|
||||
. '$paypal_client_secret = ' . $q($vals['paypal_client_secret'] ?? '') . '; // Your PayPal Client Secret' . "\n"
|
||||
. '$paypal_webhook_id = ' . $q($vals['paypal_webhook_id'] ?? '') . '; // Your PayPal Webhook ID' . "\n"
|
||||
. "\n"
|
||||
. '// Admin config backup retention: how many backups to keep (1–10). Default 5.' . "\n"
|
||||
. '$SITE_CONFIG_BACKUP_RETENTION = ' . $retention . ';' . "\n"
|
||||
. '?>' . "\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read current values from config (already loaded by config_loader above).
|
||||
// ---------------------------------------------------------------------------
|
||||
$cfgVals = [
|
||||
'SITE_BASE_URL' => $SITE_BASE_URL ?? '',
|
||||
'SITE_BACKGROUND' => $SITE_BACKGROUND ?? 'images/dark.jpg',
|
||||
'SITE_DATA_DIR' => isset($SITE_DATA_DIR) ? $SITE_DATA_DIR : '',
|
||||
'paypal_sandbox' => $paypal_sandbox ?? true,
|
||||
'paypal_client_id' => $paypal_client_id ?? '',
|
||||
'paypal_client_secret' => $paypal_client_secret ?? '',
|
||||
'paypal_webhook_id' => $paypal_webhook_id ?? '',
|
||||
'backup_retention' => $SITE_CONFIG_BACKUP_RETENTION ?? 5,
|
||||
];
|
||||
|
||||
// Detect panel-mode (DB settings are managed by the panel)
|
||||
$panelMode = defined('BILLING_PANEL_CONFIG_PATH');
|
||||
$panelCfgPath = $panelMode ? BILLING_PANEL_CONFIG_PATH : null;
|
||||
|
||||
$status = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$token = $_POST['csrf'] ?? '';
|
||||
if (!hash_equals($csrf, (string)$token)) {
|
||||
$status = 'Invalid CSRF token.';
|
||||
} else {
|
||||
if (!is_writable($cfgPath)) {
|
||||
$status = 'Config file not writable: ' . h($cfgPath);
|
||||
} else {
|
||||
// Backup
|
||||
$bakDir = dirname($cfgPath) . '/backups';
|
||||
@mkdir($bakDir, 0775, true);
|
||||
$bakName = $bakDir . '/config.inc.php.' . date('Ymd-His') . '.' . bin2hex(random_bytes(4)) . '.bak';
|
||||
if (!copy($cfgPath, $bakName)) {
|
||||
$status = 'Failed to create backup. Aborting.';
|
||||
} else {
|
||||
$new = $_POST['config_text'] ?? '';
|
||||
// Basic safety: ensure the file still starts with <?php
|
||||
if (strpos(trim($new), '<?php') !== 0) {
|
||||
$status = 'Config must start with <?php';
|
||||
} else {
|
||||
if (file_put_contents($cfgPath, $new) === false) {
|
||||
$status = 'Failed to write config file.';
|
||||
} else {
|
||||
// Post-save syntax check: try to run php -l using a sensible PHP executable.
|
||||
$phpExec = PHP_BINARY ?: (file_exists('C:\\xampp\\php\\php.exe') ? 'C:\\xampp\\php\\php.exe' : null);
|
||||
$lintOk = true;
|
||||
$lintOutput = '';
|
||||
if ($phpExec) {
|
||||
$cmd = escapeshellarg($phpExec) . ' -l ' . escapeshellarg($cfgPath);
|
||||
// execute and capture output
|
||||
$out = null; $rc = null;
|
||||
@exec($cmd . ' 2>&1', $out, $rc);
|
||||
$lintOutput = is_array($out) ? implode("\n", $out) : (string)$out;
|
||||
if ($rc !== 0) {
|
||||
$lintOk = false;
|
||||
}
|
||||
} else {
|
||||
$lintOutput = 'PHP executable not found for linting; skipping post-save syntax check.';
|
||||
}
|
||||
$statusType = 'info'; // 'success' | 'error' | 'info'
|
||||
|
||||
if (!$lintOk) {
|
||||
// rollback
|
||||
@copy($bakName, $cfgPath);
|
||||
$status = 'Syntax error detected in saved config. Changes rolled back. Lint output: ' . h($lintOutput);
|
||||
} else {
|
||||
$status = 'Config saved successfully. Backup: ' . basename($bakName) . (strlen($lintOutput) ? ' (lint: '.h($lintOutput).')' : '');
|
||||
// reload values
|
||||
require_once($cfgPath);
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST: Save interactive form
|
||||
// ---------------------------------------------------------------------------
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_form') {
|
||||
$token = $_POST['csrf'] ?? '';
|
||||
if (!hash_equals($csrf, (string)$token)) {
|
||||
$status = 'Invalid CSRF token.';
|
||||
$statusType = 'error';
|
||||
} elseif (!is_writable($cfgPath)) {
|
||||
$status = 'Config file is not writable: ' . h($cfgPath);
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
// Collect and validate form values
|
||||
$formVals = [
|
||||
'SITE_BASE_URL' => trim($_POST['SITE_BASE_URL'] ?? ''),
|
||||
'SITE_BACKGROUND' => trim($_POST['SITE_BACKGROUND'] ?? 'images/dark.jpg'),
|
||||
'SITE_DATA_DIR' => trim($_POST['SITE_DATA_DIR'] ?? ''),
|
||||
'paypal_sandbox' => (($_POST['paypal_sandbox'] ?? 'true') === 'true'),
|
||||
'paypal_client_id' => trim($_POST['paypal_client_id'] ?? ''),
|
||||
'paypal_client_secret' => trim($_POST['paypal_client_secret'] ?? ''),
|
||||
'paypal_webhook_id' => trim($_POST['paypal_webhook_id'] ?? ''),
|
||||
'backup_retention' => (int)($_POST['backup_retention'] ?? 5),
|
||||
];
|
||||
|
||||
// Validate
|
||||
$validationError = '';
|
||||
if ($formVals['backup_retention'] < 1 || $formVals['backup_retention'] > 10) {
|
||||
$validationError = 'Backup retention must be a number between 1 and 10.';
|
||||
}
|
||||
|
||||
if ($validationError) {
|
||||
$status = $validationError;
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
$existingContent = (string)file_get_contents($cfgPath);
|
||||
$newContent = billing_admin_build_config($existingContent, $formVals);
|
||||
|
||||
// Backup before write.
|
||||
// Note: the backup copy and subsequent file_put_contents are not covered by a
|
||||
// single atomic lock. This is acceptable for an admin-only operation where
|
||||
// concurrent writes are not expected.
|
||||
$bakName = billing_admin_create_backup($cfgPath, $bakDir);
|
||||
if (!$bakName) {
|
||||
$status = 'Failed to create backup. Aborting save.';
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
if (file_put_contents($cfgPath, $newContent, LOCK_EX) === false) {
|
||||
$status = 'Failed to write config file.';
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
// Syntax check
|
||||
[$lintOk, $lintOut] = billing_admin_lint($cfgPath);
|
||||
if (!$lintOk) {
|
||||
@copy($bakName, $cfgPath); // rollback
|
||||
$status = 'Syntax error in generated config; rolled back. Lint: ' . h($lintOut);
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
// Apply backup retention
|
||||
$retention = max(1, min(10, $formVals['backup_retention']));
|
||||
billing_admin_apply_retention($bakDir, $retention);
|
||||
|
||||
$cfgVals = $formVals; // update displayed values
|
||||
$status = 'Config saved successfully. Backup: ' . basename($bakName);
|
||||
$statusType = 'success';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST: Save raw editor
|
||||
// ---------------------------------------------------------------------------
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_raw') {
|
||||
$token = $_POST['csrf'] ?? '';
|
||||
if (!hash_equals($csrf, (string)$token)) {
|
||||
$status = 'Invalid CSRF token.';
|
||||
$statusType = 'error';
|
||||
} elseif (!is_writable($cfgPath)) {
|
||||
$status = 'Config file is not writable: ' . h($cfgPath);
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
$newRaw = $_POST['config_text'] ?? '';
|
||||
if (strpos(trim($newRaw), '<?php') !== 0) {
|
||||
$status = 'Config must start with <?php';
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
// Backup then write (admin-only operation; concurrent writes are not expected).
|
||||
$bakName = billing_admin_create_backup($cfgPath, $bakDir);
|
||||
if (!$bakName) {
|
||||
$status = 'Failed to create backup. Aborting save.';
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
if (file_put_contents($cfgPath, $newRaw, LOCK_EX) === false) {
|
||||
$status = 'Failed to write config file.';
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
[$lintOk, $lintOut] = billing_admin_lint($cfgPath);
|
||||
if (!$lintOk) {
|
||||
@copy($bakName, $cfgPath); // rollback
|
||||
$status = 'Syntax error detected; changes rolled back. Lint: ' . h($lintOut);
|
||||
$statusType = 'error';
|
||||
} else {
|
||||
// Apply backup retention from config
|
||||
$retentionNow = max(1, min(10, (int)($SITE_CONFIG_BACKUP_RETENTION ?? 5)));
|
||||
billing_admin_apply_retention($bakDir, $retentionNow);
|
||||
|
||||
$status = 'Config saved successfully. Backup: ' . basename($bakName);
|
||||
$statusType = 'success';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always read current raw content from disk for the raw editor
|
||||
$currentText = '';
|
||||
if (is_readable($cfgPath)) {
|
||||
$currentText = file_get_contents($cfgPath);
|
||||
$currentText = file_get_contents($cfgPath);
|
||||
}
|
||||
|
||||
// List current backups for display
|
||||
$bakFiles = is_dir($bakDir) ? (array)glob($bakDir . '/*.bak') : [];
|
||||
rsort($bakFiles); // newest first
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
|
@ -86,20 +317,232 @@ if (is_readable($cfgPath)) {
|
|||
<title>Admin — Edit Config</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="css/header.css">
|
||||
<style>
|
||||
.cfg-section { background:#fff; border:1px solid #ddd; border-radius:6px; padding:20px 24px; margin-bottom:28px; }
|
||||
.cfg-section h2 { margin-top:0; color:#333; border-bottom:2px solid #eee; padding-bottom:8px; }
|
||||
.field-group { margin-bottom:18px; }
|
||||
.field-group label { display:block; font-weight:600; color:#333; margin-bottom:4px; }
|
||||
.field-help { font-size:0.85em; color:#666; margin-bottom:6px; }
|
||||
.field-group input[type=text],
|
||||
.field-group input[type=password],
|
||||
.field-group input[type=number],
|
||||
.field-group select { width:100%; max-width:520px; padding:8px 10px; border:1px solid #ccc;
|
||||
border-radius:4px; font-size:1em; box-sizing:border-box; }
|
||||
.field-group .pw-wrap { display:flex; gap:6px; align-items:center; max-width:520px; }
|
||||
.field-group .pw-wrap input { flex:1; }
|
||||
.btn-show { padding:8px 14px; font-size:0.9em; border:1px solid #aaa; border-radius:4px;
|
||||
background:#f5f5f5; cursor:pointer; white-space:nowrap; }
|
||||
.status-box { padding:12px 16px; border-radius:4px; margin-bottom:18px; font-weight:600; }
|
||||
.status-success { background:#d4edda; color:#155724; border:1px solid #c3e6cb; }
|
||||
.status-error { background:#f8d7da; color:#721c24; border:1px solid #f5c6cb; }
|
||||
.status-info { background:#d1ecf1; color:#0c5460; border:1px solid #bee5eb; }
|
||||
.panel-badge { background:#e8f4fd; border:1px solid #9ec8f0; color:#1a5276; padding:10px 14px;
|
||||
border-radius:4px; margin-bottom:18px; font-size:0.9em; }
|
||||
.readonly-field { background:#f4f4f4; color:#555; cursor:not-allowed; }
|
||||
.warn-box { background:#fff3cd; border:1px solid #ffc107; color:#856404; padding:10px 14px;
|
||||
border-radius:4px; margin-bottom:14px; font-size:0.9em; }
|
||||
.save-row { margin:14px 0; }
|
||||
.save-row button { padding:10px 24px; font-size:1em; font-weight:600; }
|
||||
.bak-list { font-size:0.85em; color:#555; margin-top:4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-wide panel">
|
||||
<h1>Edit Site Config</h1>
|
||||
<?php if ($status): ?><div class="panel"><strong><?php echo h($status); ?></strong></div><?php endif; ?>
|
||||
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
|
||||
<div style="margin-bottom:8px;"><button type="submit">Save Config</button></div>
|
||||
<textarea name="config_text" rows="24" style="width:100%;font-family:monospace;"><?php echo h($currentText); ?></textarea>
|
||||
<div style="margin-top:8px;"><button type="submit">Save Config</button></div>
|
||||
</form>
|
||||
<?php if ($status): ?>
|
||||
<div class="status-box status-<?php echo h($statusType); ?>"><?php echo h($status); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($billing_config_warning)): ?>
|
||||
<div class="warn-box">⚠️ <?php echo h($billing_config_warning); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ===================================================================
|
||||
SECTION A: Interactive form
|
||||
==================================================================== -->
|
||||
<div class="cfg-section">
|
||||
<h2>Site Settings</h2>
|
||||
|
||||
<?php if ($panelMode): ?>
|
||||
<div class="panel-badge">
|
||||
ℹ️ <strong>Panel-integrated mode.</strong>
|
||||
Database settings are managed by the panel and synced automatically from
|
||||
<code><?php echo h($panelCfgPath); ?></code>.
|
||||
They are shown below for reference only.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
|
||||
<input type="hidden" name="action" value="save_form">
|
||||
|
||||
<!-- DB read-only info (panel mode) -->
|
||||
<?php if ($panelMode): ?>
|
||||
<div class="field-group">
|
||||
<label>Database Host</label>
|
||||
<div class="field-help">Managed by the panel config. Edit the panel's <code>includes/config.inc.php</code> to change.</div>
|
||||
<input type="text" class="readonly-field" value="<?php echo h((string)($db_host ?? '')); ?>" readonly>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label>Database Name</label>
|
||||
<input type="text" class="readonly-field" value="<?php echo h((string)($db_name ?? '')); ?>" readonly>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label>Table Prefix</label>
|
||||
<input type="text" class="readonly-field" value="<?php echo h((string)($table_prefix ?? '')); ?>" readonly>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Site Base URL -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_base_url">Site Base URL</label>
|
||||
<div class="field-help">
|
||||
Full base URL without trailing slash (e.g. <code>https://gameservers.world</code>).
|
||||
Leave empty to use relative paths.
|
||||
</div>
|
||||
<input type="text" id="cfg_base_url" name="SITE_BASE_URL"
|
||||
value="<?php echo h((string)$cfgVals['SITE_BASE_URL']); ?>"
|
||||
placeholder="https://example.com">
|
||||
</div>
|
||||
|
||||
<!-- Site Background -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_bg">Site Background Image</label>
|
||||
<div class="field-help">
|
||||
Path to background image relative to the billing site root (e.g. <code>images/dark.jpg</code>).
|
||||
</div>
|
||||
<input type="text" id="cfg_bg" name="SITE_BACKGROUND"
|
||||
value="<?php echo h((string)$cfgVals['SITE_BACKGROUND']); ?>"
|
||||
placeholder="images/dark.jpg">
|
||||
</div>
|
||||
|
||||
<!-- Data Directory -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_datadir">Site Data Directory</label>
|
||||
<div class="field-help">
|
||||
Absolute path where payment webhook JSON files are stored.
|
||||
Leave empty to use the default: <code>modules/billing/data/</code>.
|
||||
</div>
|
||||
<input type="text" id="cfg_datadir" name="SITE_DATA_DIR"
|
||||
value="<?php echo h((string)$cfgVals['SITE_DATA_DIR']); ?>"
|
||||
placeholder="(default: billing/data/)">
|
||||
</div>
|
||||
|
||||
<hr style="border:none;border-top:1px solid #eee;margin:24px 0;">
|
||||
<h3 style="margin-top:0;color:#333;">PayPal Configuration</h3>
|
||||
|
||||
<!-- PayPal Sandbox -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_sandbox">PayPal Mode</label>
|
||||
<div class="field-help">
|
||||
Use <strong>Sandbox</strong> for testing, <strong>Live</strong> for real payments.
|
||||
Make sure you use the matching Client ID and Secret for the selected mode.
|
||||
</div>
|
||||
<select id="cfg_sandbox" name="paypal_sandbox">
|
||||
<option value="true" <?php echo $cfgVals['paypal_sandbox'] ? 'selected' : ''; ?>>Sandbox (test mode)</option>
|
||||
<option value="false" <?php echo !$cfgVals['paypal_sandbox'] ? 'selected' : ''; ?>>Live (real payments)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- PayPal Client ID -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_cid">PayPal Client ID</label>
|
||||
<div class="field-help">
|
||||
Your PayPal app Client ID. Safe to expose in browser JS.
|
||||
Found in your PayPal Developer Dashboard under your app credentials.
|
||||
</div>
|
||||
<input type="text" id="cfg_cid" name="paypal_client_id"
|
||||
value="<?php echo h((string)$cfgVals['paypal_client_id']); ?>"
|
||||
placeholder="AY... or AZ...">
|
||||
</div>
|
||||
|
||||
<!-- PayPal Client Secret -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_csecret">PayPal Client Secret</label>
|
||||
<div class="field-help">
|
||||
Your PayPal app Client Secret. <strong>Server-side only</strong> — never sent to the browser.
|
||||
</div>
|
||||
<div class="pw-wrap">
|
||||
<input type="password" id="cfg_csecret" name="paypal_client_secret"
|
||||
value="<?php echo h((string)$cfgVals['paypal_client_secret']); ?>"
|
||||
autocomplete="new-password">
|
||||
<button type="button" class="btn-show"
|
||||
onclick="var f=document.getElementById('cfg_csecret');f.type=f.type==='password'?'text':'password';this.textContent=f.type==='password'?'Show':'Hide';">
|
||||
Show
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PayPal Webhook ID -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_wh">PayPal Webhook ID</label>
|
||||
<div class="field-help">
|
||||
Webhook ID from your PayPal app (used for webhook signature verification).
|
||||
Leave empty to skip signature verification (not recommended for production).
|
||||
</div>
|
||||
<input type="text" id="cfg_wh" name="paypal_webhook_id"
|
||||
value="<?php echo h((string)$cfgVals['paypal_webhook_id']); ?>"
|
||||
placeholder="Webhook ID">
|
||||
</div>
|
||||
|
||||
<hr style="border:none;border-top:1px solid #eee;margin:24px 0;">
|
||||
<h3 style="margin-top:0;color:#333;">Backup Settings</h3>
|
||||
|
||||
<!-- Backup Retention -->
|
||||
<div class="field-group">
|
||||
<label for="cfg_retention">Config Backup Retention</label>
|
||||
<div class="field-help">
|
||||
Number of config backups to keep (1–10). The oldest backup beyond this limit is
|
||||
deleted after each save. Backups are stored in
|
||||
<code><?php echo h($bakDir); ?></code>.
|
||||
</div>
|
||||
<input type="number" id="cfg_retention" name="backup_retention"
|
||||
value="<?php echo (int)$cfgVals['backup_retention']; ?>"
|
||||
min="1" max="10" style="max-width:100px;">
|
||||
</div>
|
||||
|
||||
<div class="save-row">
|
||||
<button type="submit">💾 Save Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ===================================================================
|
||||
SECTION B: Raw PHP editor
|
||||
==================================================================== -->
|
||||
<div class="cfg-section">
|
||||
<h2>Advanced: Raw Config Editor</h2>
|
||||
<div class="warn-box">
|
||||
⚠️ <strong>Warning:</strong> Manually editing the raw PHP file can break the billing
|
||||
website if you introduce a syntax error or remove required variables.
|
||||
A backup is created automatically before saving, and a syntax check runs after.
|
||||
The file is rolled back if a parse error is detected.
|
||||
</div>
|
||||
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="csrf" value="<?php echo h($csrf); ?>">
|
||||
<input type="hidden" name="action" value="save_raw">
|
||||
<div class="save-row"><button type="submit">💾 Save Raw Config</button></div>
|
||||
<textarea name="config_text" rows="28"
|
||||
style="width:100%;font-family:monospace;font-size:0.9em;border:1px solid #ccc;border-radius:4px;padding:10px;box-sizing:border-box;"
|
||||
><?php echo h((string)$currentText); ?></textarea>
|
||||
<div class="save-row"><button type="submit">💾 Save Raw Config</button></div>
|
||||
</form>
|
||||
|
||||
<p style="margin-top:16px;">
|
||||
<strong>Backup directory:</strong> <code><?php echo h($bakDir); ?></code>
|
||||
<?php if ($bakFiles): ?>
|
||||
<br><span class="bak-list">
|
||||
<?php echo count($bakFiles); ?> backup(s) stored.
|
||||
Most recent: <code><?php echo h(basename($bakFiles[0])); ?></code>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<br><span class="bak-list">No backups yet.</span>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p><small>Backups are stored in <code><?php echo h(dirname($cfgPath) . '/backups'); ?></code></small></p>
|
||||
</div>
|
||||
<?php include(__DIR__ . '/includes/footer.php'); ?>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -41,3 +41,7 @@ $paypal_sandbox = true; // Set to false for live payments
|
|||
$paypal_client_id = ''; // Your PayPal Client ID
|
||||
$paypal_client_secret = ''; // Your PayPal Client Secret
|
||||
$paypal_webhook_id = ''; // Your PayPal Webhook ID (for webhook signature verification)
|
||||
|
||||
# --- Admin config backup retention ---
|
||||
# Number of config backups to keep (1–10). Oldest backups beyond this limit are deleted.
|
||||
$SITE_CONFIG_BACKUP_RETENTION = 5;
|
||||
|
|
|
|||
|
|
@ -36,4 +36,7 @@ $paypal_sandbox = true; // Set to false for live payments
|
|||
$paypal_client_id = ''; // Your PayPal Client ID
|
||||
$paypal_client_secret = ''; // Your PayPal Client Secret
|
||||
$paypal_webhook_id = ''; // Your PayPal Webhook ID (for webhook signature verification)
|
||||
|
||||
// Admin config backup retention: how many backups to keep (1–10). Default 5.
|
||||
$SITE_CONFIG_BACKUP_RETENTION = 5;
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -2,73 +2,235 @@
|
|||
/**
|
||||
* Billing config loader
|
||||
*
|
||||
* Priority order (panel-first):
|
||||
* 1. <panel_root>/includes/config.inc.php (active panel config — always wins when present)
|
||||
* 2. modules/billing/includes/config.inc.php (local billing config — standalone fallback only)
|
||||
* Load order:
|
||||
* 1. modules/billing/includes/config.inc.php — always loaded first; contains billing-specific
|
||||
* settings (PayPal credentials, SITE_BASE_URL, SITE_BACKGROUND, SITE_DATA_DIR, backup
|
||||
* retention, and default DB settings for standalone installs).
|
||||
* 2. <panel_root>/includes/config.inc.php — read via regex (no side-effects) when the
|
||||
* billing module is installed inside a GSP panel tree. DB variables extracted from the
|
||||
* panel config override the billing config DB variables in memory so that the module
|
||||
* always connects to the active panel database.
|
||||
*
|
||||
* The panel config is preferred so that every billing page, migration, and schema
|
||||
* check automatically uses the database from the active installation. This prevents
|
||||
* a testing install from accidentally writing to a production database when the local
|
||||
* billing config.inc.php still contains hard-coded production credentials.
|
||||
* Panel-child detection:
|
||||
* Walk up from modules/billing/includes/ looking for the pattern
|
||||
* <ancestor>/includes/config.inc.php that contains the GSP panel DB variables
|
||||
* ($db_host, $db_user, $db_name, $table_prefix). Stop after six levels.
|
||||
*
|
||||
* Standalone deployments (billing module deployed without the panel) should place
|
||||
* their own config.inc.php in modules/billing/includes/ as a fallback.
|
||||
* Config sync:
|
||||
* If the panel config DB variables differ from the billing config file on disk, the loader
|
||||
* updates only the DB variable lines in billing/includes/config.inc.php so that subsequent
|
||||
* page loads (and standalone tools) always see current credentials. The sync only runs when
|
||||
* the file is writable. If it cannot write, a non-fatal admin-visible warning is set in
|
||||
* $billing_config_warning.
|
||||
*
|
||||
* Standalone installs:
|
||||
* When no panel config is found the billing config is used as-is; standalone mode is fully
|
||||
* supported.
|
||||
*/
|
||||
if (defined('BILLING_CONFIG_LOADED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attempted = [];
|
||||
|
||||
// Prefer the panel config so the billing module always uses the active installation's DB.
|
||||
$panelConfig = null;
|
||||
$projectRoot = realpath(__DIR__ . '/../../..');
|
||||
if ($projectRoot !== false) {
|
||||
$panelConfig = $projectRoot . '/includes/config.inc.php';
|
||||
} else {
|
||||
$panelConfig = __DIR__ . '/../../../includes/config.inc.php';
|
||||
}
|
||||
|
||||
if ($panelConfig && is_readable($panelConfig)) {
|
||||
$attempted[] = $panelConfig;
|
||||
require_once $panelConfig;
|
||||
if (!defined('BILLING_CONFIG_PATH')) {
|
||||
define('BILLING_CONFIG_PATH', $panelConfig);
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: extract DB variable values from a PHP config file without including it.
|
||||
// Uses regex on the raw file text so there are no side-effects.
|
||||
// ---------------------------------------------------------------------------
|
||||
if (!function_exists('_billing_extract_db_vars_from_file')) {
|
||||
function _billing_extract_db_vars_from_file(string $path): array
|
||||
{
|
||||
$content = @file_get_contents($path);
|
||||
if ($content === false) {
|
||||
return [];
|
||||
}
|
||||
$result = [];
|
||||
foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'] as $var) {
|
||||
// Match: $varname = "value"; or $varname="value"; (single or double quotes, no escaped quotes)
|
||||
// Note: credentials containing escaped quotes or special chars are not supported by this
|
||||
// regex-based extractor — use var_export() to write values and keep creds simple.
|
||||
if (preg_match('/^\s*\$' . preg_quote($var, '/') . '\s*=\s*"([^"]*)"/m', $content, $m) ||
|
||||
preg_match('/^\s*\$' . preg_quote($var, '/') . "\s*=\s*'([^']*)'/m", $content, $m)) {
|
||||
$result[$var] = $m[1];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
define('BILLING_CONFIG_LOADED', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$attempted[] = $panelConfig;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: update DB variable lines in the billing config file without touching
|
||||
// any other settings. Returns true when the file was updated, false otherwise.
|
||||
// ---------------------------------------------------------------------------
|
||||
if (!function_exists('_billing_sync_db_vars_to_file')) {
|
||||
function _billing_sync_db_vars_to_file(string $filePath, array $panelVars): bool
|
||||
{
|
||||
if (!is_writable($filePath)) {
|
||||
return false;
|
||||
}
|
||||
$content = file_get_contents($filePath);
|
||||
if ($content === false) {
|
||||
return false;
|
||||
}
|
||||
$changed = false;
|
||||
foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'] as $var) {
|
||||
if (!array_key_exists($var, $panelVars)) {
|
||||
continue;
|
||||
}
|
||||
$newVal = $panelVars[$var];
|
||||
// Match any existing assignment for this var (double or single quotes, no escaped quotes)
|
||||
// Use var_export() to produce the replacement value so special characters are handled
|
||||
// correctly (var_export produces a valid PHP string literal).
|
||||
$pattern = '/^(\s*\$' . preg_quote($var, '/') . '\s*=\s*)["\'][^"\']*["\'](.*)$/m';
|
||||
$exportedVal = var_export($newVal, true); // produces 'value' with proper escaping
|
||||
$newLine = '${1}' . str_replace('\\', '\\\\', $exportedVal) . '${2}';
|
||||
$updated = preg_replace($pattern, $newLine, $content, 1, $count);
|
||||
if ($count > 0 && $updated !== $content) {
|
||||
$content = (string)$updated;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
if (!$changed) {
|
||||
return false;
|
||||
}
|
||||
return file_put_contents($filePath, $content, LOCK_EX) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: locate the panel config by walking up ancestor directories.
|
||||
// Returns an absolute path when found, or null.
|
||||
// ---------------------------------------------------------------------------
|
||||
if (!function_exists('_billing_find_panel_config')) {
|
||||
function _billing_find_panel_config(string $startDir): ?string
|
||||
{
|
||||
$dir = realpath($startDir);
|
||||
if ($dir === false) {
|
||||
return null;
|
||||
}
|
||||
// Walk up at most 6 levels (covers: includes/ → billing/ → modules/ → panel_root/)
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$candidate = $dir . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'config.inc.php';
|
||||
if (is_readable($candidate)) {
|
||||
// Confirm it is a GSP/panel config by looking for $db_host and $table_prefix
|
||||
$content = @file_get_contents($candidate);
|
||||
if ($content !== false &&
|
||||
strpos($content, '$db_host') !== false &&
|
||||
strpos($content, '$table_prefix') !== false) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
$parent = dirname($dir);
|
||||
if ($parent === $dir) {
|
||||
break; // reached filesystem root
|
||||
}
|
||||
$dir = $parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 1: Load the billing config (always — it holds billing-specific settings).
|
||||
// ---------------------------------------------------------------------------
|
||||
$billing_config_warning = null; // surfaced to admin pages when non-null
|
||||
|
||||
// Fallback: local billing config (useful for standalone deployments where no panel is present).
|
||||
$localConfig = __DIR__ . '/config.inc.php';
|
||||
|
||||
if (is_readable($localConfig)) {
|
||||
$attempted[] = $localConfig;
|
||||
require_once $localConfig;
|
||||
if (!defined('BILLING_CONFIG_PATH')) {
|
||||
define('BILLING_CONFIG_PATH', $localConfig);
|
||||
if (!is_readable($localConfig)) {
|
||||
// No billing config found — render an informative error for the admin.
|
||||
$message = "GSP Billing module cannot find modules/billing/includes/config.inc.php.\n";
|
||||
$message .= "Expected: " . $localConfig . "\n";
|
||||
$message .= "\nCreate the file from the example (config.example.php) and fill in your settings.\n";
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: text/plain; charset=UTF-8', true, 500);
|
||||
}
|
||||
define('BILLING_CONFIG_LOADED', true);
|
||||
return;
|
||||
echo $message;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$attempted[] = $localConfig;
|
||||
require_once $localConfig;
|
||||
|
||||
$message = "GSP Billing module cannot find config.inc.php.\n";
|
||||
$message .= "Looked in:\n";
|
||||
foreach ((array)$attempted as $path) {
|
||||
if (!$path) {
|
||||
continue;
|
||||
if (!defined('BILLING_CONFIG_PATH')) {
|
||||
define('BILLING_CONFIG_PATH', $localConfig);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 2: Child-of-panel detection.
|
||||
// ---------------------------------------------------------------------------
|
||||
$_billing_panel_config = _billing_find_panel_config(dirname(__DIR__, 2));
|
||||
|
||||
if ($_billing_panel_config !== null) {
|
||||
// Found a panel config — extract its DB variables (no side-effects).
|
||||
$panelDbVars = _billing_extract_db_vars_from_file($_billing_panel_config);
|
||||
|
||||
if (!empty($panelDbVars)) {
|
||||
// Override DB settings in the current scope with panel values.
|
||||
foreach ($panelDbVars as $_bk => $_bv) {
|
||||
$$_bk = $_bv;
|
||||
}
|
||||
unset($_bk, $_bv);
|
||||
|
||||
// Record which panel config was found (admin pages may display this).
|
||||
if (!defined('BILLING_PANEL_CONFIG_PATH')) {
|
||||
define('BILLING_PANEL_CONFIG_PATH', $_billing_panel_config);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Step 3: Config sync — keep billing config.inc.php DB vars in sync.
|
||||
// Only rewrite the file when the on-disk values actually differ so that
|
||||
// we never touch the file on normal page loads where nothing changed.
|
||||
// -------------------------------------------------------------------
|
||||
$diskVars = _billing_extract_db_vars_from_file($localConfig);
|
||||
$needsSync = false;
|
||||
foreach ($panelDbVars as $k => $v) {
|
||||
if (!array_key_exists($k, $diskVars) || $diskVars[$k] !== $v) {
|
||||
$needsSync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($needsSync) {
|
||||
if (!_billing_sync_db_vars_to_file($localConfig, $panelDbVars)) {
|
||||
// Non-fatal: show admin warning; runtime DB vars are already overridden above.
|
||||
$billing_config_warning =
|
||||
'Panel DB settings differ from billing/includes/config.inc.php but the file '
|
||||
. 'is not writable. The billing module will use the panel DB settings for this '
|
||||
. 'request, but consider updating file permissions or manually editing config.inc.php '
|
||||
. 'to match the panel config at: ' . $_billing_panel_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
$message .= " - " . $path . "\n";
|
||||
}
|
||||
$message .= "\nCopy your panel's includes/config.inc.php into modules/billing/includes/config.inc.php ";
|
||||
$message .= "or ensure the panel config is readable so the billing pages can load database settings.\n";
|
||||
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: text/plain; charset=UTF-8', true, 500);
|
||||
unset($_billing_panel_config, $panelDbVars, $diskVars, $needsSync, $k, $v);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 4: Apply safe defaults for billing-specific variables that may be absent
|
||||
// in older config files (never overwrite values already set by the config).
|
||||
// ---------------------------------------------------------------------------
|
||||
if (!isset($paypal_sandbox)) {
|
||||
$paypal_sandbox = true;
|
||||
}
|
||||
echo $message;
|
||||
exit(1);
|
||||
if (!isset($paypal_client_id)) {
|
||||
$paypal_client_id = '';
|
||||
}
|
||||
if (!isset($paypal_client_secret)) {
|
||||
$paypal_client_secret = '';
|
||||
}
|
||||
if (!isset($paypal_webhook_id)) {
|
||||
$paypal_webhook_id = '';
|
||||
}
|
||||
if (!isset($SITE_BASE_URL)) {
|
||||
$SITE_BASE_URL = '';
|
||||
}
|
||||
if (!isset($SITE_BACKGROUND)) {
|
||||
$SITE_BACKGROUND = 'images/dark.jpg';
|
||||
}
|
||||
if (!isset($SITE_DATA_DIR)) {
|
||||
$SITE_DATA_DIR = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'data';
|
||||
}
|
||||
if (!isset($SITE_CONFIG_BACKUP_RETENTION) || !is_int($SITE_CONFIG_BACKUP_RETENTION)) {
|
||||
$SITE_CONFIG_BACKUP_RETENTION = 5;
|
||||
}
|
||||
$SITE_CONFIG_BACKUP_RETENTION = max(1, min(10, (int)$SITE_CONFIG_BACKUP_RETENTION));
|
||||
|
||||
define('BILLING_CONFIG_LOADED', true);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Last Updated at 3:18pm on 2026-05-06
|
||||
Last Updated at 3:52pm on 2026-05-06
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue