diff --git a/modules/billing/admin.php b/modules/billing/admin.php index 42b0bf1b..b2a0241b 100644 --- a/modules/billing/admin.php +++ b/modules/billing/admin.php @@ -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'); diff --git a/modules/billing/admin_config.php b/modules/billing/admin_config.php index 8cf3aa15..515ef13c 100644 --- a/modules/billing/admin_config.php +++ b/modules/billing/admin_config.php @@ -1,5 +1,20 @@ 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 ' 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 &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), ' @@ -86,20 +317,232 @@ if (is_readable($cfgPath)) { Admin — Edit Config +

Edit Site Config

-
-
- -
- -
-
+ +
+ + + +
⚠️
+ + + +
+

Site Settings

+ + +
+ ℹ️ Panel-integrated mode. + Database settings are managed by the panel and synced automatically from + . + They are shown below for reference only. +
+ + +
+ + + + + +
+ +
Managed by the panel config. Edit the panel's includes/config.inc.php to change.
+ +
+
+ + +
+
+ + +
+ + + +
+ +
+ Full base URL without trailing slash (e.g. https://gameservers.world). + Leave empty to use relative paths. +
+ +
+ + +
+ +
+ Path to background image relative to the billing site root (e.g. images/dark.jpg). +
+ +
+ + +
+ +
+ Absolute path where payment webhook JSON files are stored. + Leave empty to use the default: modules/billing/data/. +
+ +
+ +
+

PayPal Configuration

+ + +
+ +
+ Use Sandbox for testing, Live for real payments. + Make sure you use the matching Client ID and Secret for the selected mode. +
+ +
+ + +
+ +
+ Your PayPal app Client ID. Safe to expose in browser JS. + Found in your PayPal Developer Dashboard under your app credentials. +
+ +
+ + +
+ +
+ Your PayPal app Client Secret. Server-side only — never sent to the browser. +
+
+ + +
+
+ + +
+ +
+ Webhook ID from your PayPal app (used for webhook signature verification). + Leave empty to skip signature verification (not recommended for production). +
+ +
+ +
+

Backup Settings

+ + +
+ +
+ Number of config backups to keep (1–10). The oldest backup beyond this limit is + deleted after each save. Backups are stored in + . +
+ +
+ +
+ +
+
+
+ + +
+

Advanced: Raw Config Editor

+
+ ⚠️ Warning: 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. +
+ +
+ + +
+ +
+
+ +

+ Backup directory: + +
+ backup(s) stored. + Most recent: + + +
No backups yet. + +

+
-

Backups are stored in

diff --git a/modules/billing/includes/config.example.php b/modules/billing/includes/config.example.php index a675b925..dbee9b4f 100644 --- a/modules/billing/includes/config.example.php +++ b/modules/billing/includes/config.example.php @@ -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; diff --git a/modules/billing/includes/config.inc.php b/modules/billing/includes/config.inc.php index b36a465d..b389e452 100644 --- a/modules/billing/includes/config.inc.php +++ b/modules/billing/includes/config.inc.php @@ -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; ?> diff --git a/modules/billing/includes/config_loader.php b/modules/billing/includes/config_loader.php index 3ee9f533..3f0699dc 100644 --- a/modules/billing/includes/config_loader.php +++ b/modules/billing/includes/config_loader.php @@ -2,73 +2,235 @@ /** * Billing config loader * - * Priority order (panel-first): - * 1. /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. /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 + * /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); diff --git a/modules/billing/timestamp.txt b/modules/billing/timestamp.txt index 52fc9e18..722017ae 100644 --- a/modules/billing/timestamp.txt +++ b/modules/billing/timestamp.txt @@ -1 +1 @@ -Last Updated at 3:18pm on 2026-05-06 +Last Updated at 3:52pm on 2026-05-06