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) . '"'; }; $mode = (strtolower($vals['paypal_mode'] ?? 'sandbox') === 'live') ? 'live' : 'sandbox'; $retention = max(1, min(10, (int)($vals['backup_retention'] ?? 5))); $baseUrl = rtrim(trim($vals['SITE_BASE_URL'] ?? ''), '/'); $bg = trim($vals['SITE_BACKGROUND'] ?? 'images/dark.jpg'); $dataDir = trim($vals['SITE_DATA_DIR'] ?? ''); $wh_path = '/' . ltrim(trim($vals['paypal_webhook_path'] ?? '/paypal/webhook.php'), '/'); // Sandbox credentials — never erase existing secret if field was left blank $sb_id = trim($vals['paypal_sandbox_client_id'] ?? ''); $sb_sec = trim($vals['paypal_sandbox_client_secret'] ?? ''); $sb_wh = trim($vals['paypal_sandbox_webhook_id'] ?? ''); // Live credentials — never erase existing secret if field was left blank $lv_id = trim($vals['paypal_live_client_id'] ?? ''); $lv_sec = trim($vals['paypal_live_client_secret'] ?? ''); $lv_wh = trim($vals['paypal_live_webhook_id'] ?? ''); $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 without trailing slash (e.g. https://gameservers.world).' . "\n" . '// Leave empty to use relative paths.' . "\n" . '$SITE_BASE_URL = ' . $q($baseUrl) . ';' . "\n" . '$SITE_BASE_URL = rtrim(trim((string)$SITE_BASE_URL), \'/\');' . "\n" . "\n" . '// Site-wide background image (relative to site root).' . "\n" . '$SITE_BACKGROUND = ' . $q($bg) . ';' . "\n" . '$SITE_BACKGROUND = trim((string)$SITE_BACKGROUND);' . "\n" . "\n" . '// Data directory for persisted payment webhook JSON files.' . "\n" . $dataDirLine . "\n" . "\n" . '// ---------------------------------------------------------------------------' . "\n" . '// PayPal configuration' . "\n" . '// ---------------------------------------------------------------------------' . "\n" . '$paypal_mode = ' . $q($mode) . '; // \'sandbox\' or \'live\'' . "\n" . "\n" . '// Sandbox credentials (PayPal Developer Dashboard → sandbox app)' . "\n" . '$paypal_sandbox_client_id = ' . $q($sb_id) . ';' . "\n" . '$paypal_sandbox_client_secret = ' . $q($sb_sec) . ';' . "\n" . '$paypal_sandbox_webhook_id = ' . $q($sb_wh) . ';' . "\n" . "\n" . '// Live credentials (leave blank until ready for production)' . "\n" . '$paypal_live_client_id = ' . $q($lv_id) . ';' . "\n" . '$paypal_live_client_secret = ' . $q($lv_sec) . ';' . "\n" . '$paypal_live_webhook_id = ' . $q($lv_wh) . ';' . "\n" . "\n" . '// Webhook path (relative to billing site root, must start with /)' . "\n" . '// Full public URL = $SITE_BASE_URL + $paypal_webhook_path' . "\n" . '$paypal_webhook_path = ' . $q($wh_path) . ';' . "\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' => $SITE_DATA_DIR ?? '', 'paypal_mode' => $paypal_mode ?? 'sandbox', 'paypal_sandbox_client_id' => $paypal_sandbox_client_id ?? '', 'paypal_sandbox_client_secret' => $paypal_sandbox_client_secret ?? '', 'paypal_sandbox_webhook_id' => $paypal_sandbox_webhook_id ?? '', 'paypal_live_client_id' => $paypal_live_client_id ?? '', 'paypal_live_client_secret' => $paypal_live_client_secret ?? '', 'paypal_live_webhook_id' => $paypal_live_webhook_id ?? '', 'paypal_webhook_path' => $paypal_webhook_path ?? '/paypal/webhook.php', 'backup_retention' => $SITE_CONFIG_BACKUP_RETENTION ?? 5, ]; // Computed full webhook URL for display $computedWebhookUrl = function_exists('gsp_paypal_get_full_webhook_url') ? gsp_paypal_get_full_webhook_url() : rtrim($cfgVals['SITE_BASE_URL'], '/') . $cfgVals['paypal_webhook_path']; // Detect panel-mode (DB settings are managed by the panel) $panelMode = defined('BILLING_PANEL_CONFIG_PATH'); $panelCfgPath = $panelMode ? BILLING_PANEL_CONFIG_PATH : null; $status = ''; $statusType = 'info'; // 'success' | 'error' | 'info' // --------------------------------------------------------------------------- // 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_mode' => (strtolower(trim($_POST['paypal_mode'] ?? 'sandbox')) === 'live') ? 'live' : 'sandbox', 'paypal_sandbox_client_id' => trim($_POST['paypal_sandbox_client_id'] ?? ''), 'paypal_live_client_id' => trim($_POST['paypal_live_client_id'] ?? ''), 'paypal_sandbox_webhook_id' => trim($_POST['paypal_sandbox_webhook_id'] ?? ''), 'paypal_live_webhook_id' => trim($_POST['paypal_live_webhook_id'] ?? ''), 'paypal_webhook_path' => trim($_POST['paypal_webhook_path'] ?? '/paypal/webhook.php'), 'backup_retention' => (int)($_POST['backup_retention'] ?? 5), ]; // Client secrets: only update if a non-blank value was submitted (never erase existing). $sbSecPost = trim($_POST['paypal_sandbox_client_secret'] ?? ''); $formVals['paypal_sandbox_client_secret'] = ($sbSecPost !== '') ? $sbSecPost : ($cfgVals['paypal_sandbox_client_secret'] ?? ''); $lvSecPost = trim($_POST['paypal_live_client_secret'] ?? ''); $formVals['paypal_live_client_secret'] = ($lvSecPost !== '') ? $lvSecPost : ($cfgVals['paypal_live_client_secret'] ?? ''); // 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 $computedWebhookUrl = rtrim($formVals['SITE_BASE_URL'], '/') . ('/' . ltrim($formVals['paypal_webhook_path'] ?? '/paypal/webhook.php', '/')); $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), '
.
They are shown below for reference only.
| PayPal Event ID | Type | Status | Received |
|---|---|---|---|
|
No webhook events recorded yet. Events will appear here after PayPal delivers the first webhook to .
No PayPal errors logged yet.
| Time | Context | Error Code | Message | Debug ID | Order ID | User |
|---|---|---|---|---|---|---|
|
|
|
Backup directory:
backup(s) stored.
Most recent:
No backups yet.