2025-09-25 08:37:54 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Admin tools controller
|
|
|
|
*
|
|
|
|
* Allows superusers to:
|
|
|
|
* - Enable/disable maintenance mode
|
|
|
|
* - Run database migrations
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Security and CSRF
|
|
|
|
require_once __DIR__ . '/../helpers/security.php';
|
|
|
|
$security = SecurityHelper::getInstance();
|
|
|
|
|
|
|
|
// Must be logged in
|
|
|
|
if (!Session::isValidSession()) {
|
|
|
|
header('Location: ' . $app_root . '?page=login');
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Must be superuser
|
|
|
|
$canAdmin = false;
|
|
|
|
if (isset($userId) && isset($userObject) && method_exists($userObject, 'hasRight')) {
|
|
|
|
$canAdmin = ($userId === 1) || (bool)$userObject->hasRight($userId, 'superuser');
|
|
|
|
}
|
|
|
|
if (!$canAdmin) {
|
|
|
|
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
|
|
|
|
header('Location: ' . $app_root);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2025-09-25 15:18:40 +00:00
|
|
|
// Get any old feedback messages
|
|
|
|
include __DIR__ . '/../helpers/feedback.php';
|
|
|
|
|
2025-09-25 08:37:54 +00:00
|
|
|
// Handle actions
|
|
|
|
$action = $_POST['action'] ?? '';
|
2025-09-25 09:57:02 +00:00
|
|
|
|
|
|
|
// AJAX: view migration file contents
|
|
|
|
if ($action === 'read_migration') {
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
|
|
|
// CSRF check
|
|
|
|
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
|
|
|
$csrfToken = $_POST['csrf_token'] ?? $csrfHeader;
|
|
|
|
if (!$security->verifyCsrfToken($csrfToken)) {
|
|
|
|
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Permission check
|
|
|
|
if (!$canAdmin) {
|
|
|
|
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate filename to avoid traversal
|
|
|
|
$filename = basename($_POST['filename'] ?? '');
|
|
|
|
if ($filename === '' || !preg_match('/^[A-Za-z0-9_\-]+\.sql$/', $filename)) {
|
|
|
|
echo json_encode(['success' => false, 'error' => 'Invalid filename']);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
|
|
|
$path = realpath($migrationsDir . '/' . $filename);
|
|
|
|
if ($path === false || strpos($path, realpath($migrationsDir)) !== 0) {
|
|
|
|
echo json_encode(['success' => false, 'error' => 'File not found']);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
$content = @file_get_contents($path);
|
|
|
|
if ($content === false) {
|
|
|
|
echo json_encode(['success' => false, 'error' => 'Could not read file']);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo json_encode(['success' => true, 'name' => $filename, 'content' => $content]);
|
|
|
|
exit;
|
|
|
|
}
|
2025-09-25 08:37:54 +00:00
|
|
|
if ($action !== '') {
|
|
|
|
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
|
|
|
Feedback::flash('SECURITY', 'CSRF_INVALID');
|
|
|
|
header('Location: ' . $app_root . '?page=admin-tools');
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if ($action === 'maintenance_on') {
|
|
|
|
require_once __DIR__ . '/../core/Maintenance.php';
|
|
|
|
$msg = trim($_POST['maintenance_message'] ?? '');
|
|
|
|
\App\Core\Maintenance::enable($msg);
|
|
|
|
Feedback::flash('NOTICE', 'DEFAULT', 'Maintenance mode enabled.', true);
|
|
|
|
} elseif ($action === 'maintenance_off') {
|
|
|
|
require_once __DIR__ . '/../core/Maintenance.php';
|
|
|
|
\App\Core\Maintenance::disable();
|
|
|
|
Feedback::flash('NOTICE', 'DEFAULT', 'Maintenance mode disabled.', true);
|
|
|
|
} elseif ($action === 'migrate_up') {
|
|
|
|
require_once __DIR__ . '/../core/MigrationRunner.php';
|
|
|
|
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
|
|
|
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
|
|
|
|
$applied = $runner->applyPendingMigrations();
|
|
|
|
if (empty($applied)) {
|
|
|
|
Feedback::flash('NOTICE', 'DEFAULT', 'No pending migrations.', true);
|
|
|
|
} else {
|
|
|
|
Feedback::flash('NOTICE', 'DEFAULT', 'Applied migrations: ' . implode(', ', $applied), true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
Feedback::flash('ERROR', 'DEFAULT', 'Action failed: ' . $e->getMessage(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
header('Location: ' . $app_root . '?page=admin-tools');
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare data for view
|
|
|
|
require_once __DIR__ . '/../core/Maintenance.php';
|
|
|
|
$maintenance_enabled = \App\Core\Maintenance::isEnabled();
|
|
|
|
$maintenance_message = \App\Core\Maintenance::getMessage();
|
|
|
|
|
|
|
|
require_once __DIR__ . '/../core/MigrationRunner.php';
|
|
|
|
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
|
|
|
$pending = [];
|
|
|
|
$applied = [];
|
2025-09-25 09:57:02 +00:00
|
|
|
$migration_contents = [];
|
2025-09-25 08:37:54 +00:00
|
|
|
try {
|
|
|
|
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
|
|
|
|
$pending = $runner->listPendingMigrations();
|
|
|
|
$applied = $runner->listAppliedMigrations();
|
2025-09-25 09:57:02 +00:00
|
|
|
// Preload contents for billing-admin style modals
|
|
|
|
$all = array_unique(array_merge($pending, $applied));
|
|
|
|
foreach ($all as $fname) {
|
|
|
|
$path = realpath($migrationsDir . '/' . $fname);
|
|
|
|
if ($path && strpos($path, realpath($migrationsDir)) === 0) {
|
|
|
|
$content = @file_get_contents($path);
|
|
|
|
if ($content !== false) {
|
|
|
|
$migration_contents[$fname] = $content;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-09-25 08:37:54 +00:00
|
|
|
} catch (Throwable $e) {
|
|
|
|
// show error in the page
|
|
|
|
$migration_error = $e->getMessage();
|
|
|
|
}
|
|
|
|
|
|
|
|
// CSRF token
|
|
|
|
$csrf_token = $security->generateCsrfToken();
|
|
|
|
|
|
|
|
// Load the template
|
|
|
|
include __DIR__ . '/../templates/admin-tools.php';
|