diff --git a/source/packages/com_mokosuitebackup/src/Engine/DatabaseDumper.php b/source/packages/com_mokosuitebackup/src/Engine/DatabaseDumper.php index bf49ec7..63609ca 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/DatabaseDumper.php +++ b/source/packages/com_mokosuitebackup/src/Engine/DatabaseDumper.php @@ -60,7 +60,9 @@ class DatabaseDumper $output[] = '-- Generated: ' . date('Y-m-d H:i:s'); $output[] = '-- Server: ' . $db->getServerType(); $output[] = '-- Database: ' . $db->getName(); - $output[] = '-- Prefix: ' . $prefix; + $output[] = '-- Original Prefix: ' . $prefix; + $output[] = '-- Abstract Prefix: #__'; + $output[] = '-- Note: Table names use #__ placeholder. Replace with your prefix on restore.'; $output[] = ''; $output[] = 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";'; $output[] = 'SET time_zone = "+00:00";'; @@ -90,7 +92,7 @@ class DatabaseDumper $this->tablesCount++; $output[] = '-- --------------------------------------------------------'; - $output[] = '-- Table: ' . $table; + $output[] = '-- Table: ' . $abstractName; if ($skipData) { $output[] = '-- (data excluded)'; @@ -112,8 +114,10 @@ class DatabaseDumper continue; } - $output[] = 'DROP TABLE IF EXISTS ' . $db->quoteName($table) . ';'; - $output[] = $createRow[1] . ';'; + // Replace live prefix with #__ in CREATE TABLE output + $createSql = str_replace($table, $abstractName, $createRow[1]); + $output[] = 'DROP TABLE IF EXISTS `' . $abstractName . '`;'; + $output[] = $createSql . ';'; $output[] = ''; } @@ -160,7 +164,7 @@ class DatabaseDumper } $columns = array_map([$db, 'quoteName'], array_keys($row)); - $output[] = 'INSERT INTO ' . $db->quoteName($table) + $output[] = 'INSERT INTO `' . $abstractName . '`' . ' (' . implode(', ', $columns) . ')' . ' VALUES (' . implode(', ', $values) . ');'; } diff --git a/source/packages/com_mokosuitebackup/src/Engine/DatabaseImporter.php b/source/packages/com_mokosuitebackup/src/Engine/DatabaseImporter.php index 717dce8..6009306 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/DatabaseImporter.php +++ b/source/packages/com_mokosuitebackup/src/Engine/DatabaseImporter.php @@ -87,11 +87,8 @@ class DatabaseImporter continue; } - // Replace the prefix from the dump with the current site prefix. - // The dump uses real table names (with the original prefix), but - // if restoring to a site with a different prefix we need to handle it. - // Our DatabaseDumper uses real names, so no replacement needed - // for same-site restores. + // Replace abstract #__ prefix with the current site's prefix + $statement = str_replace('#__', $prefix, $statement); try { $db->setQuery($statement); diff --git a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php index 30f1944..db95b2f 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php +++ b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php @@ -109,6 +109,52 @@ if (empty($_SESSION['restore_token'])) { $token = $_SESSION['restore_token']; +// ── Security Verification ─────────────────────────────────────────── +// Write a security file to the web root with a random code. +// The user must read the code from the file and enter it in the browser +// to prove they have filesystem access before any restore actions are allowed. +$securityFile = RESTORE_DIR . '/.mokorestore-security.php'; +$securityCode = $_SESSION['security_code'] ?? ''; + +if (empty($securityCode)) { + $securityCode = strtoupper(substr(bin2hex(random_bytes(4)), 0, 8)); + $_SESSION['security_code'] = $securityCode; + $_SESSION['security_verified'] = false; + + // Write security file with the code + $securityContent = "\n" + . "MokoRestore Security Verification\n" + . "==================================\n" + . "Code: " . $securityCode . "\n" + . "Enter this code in the MokoRestore browser interface to proceed.\n" + . "This file will be deleted automatically after verification.\n"; + file_put_contents($securityFile, $securityContent); +} + +// Handle security code verification via POST +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'verify_security') { + header('Content-Type: application/json; charset=utf-8'); + $inputCode = strtoupper(trim($_POST['security_code'] ?? '')); + + if ($inputCode === $securityCode) { + $_SESSION['security_verified'] = true; + + // Delete the security file + if (is_file($securityFile)) { + @unlink($securityFile); + } + + echo json_encode(['success' => true, 'message' => 'Security verified']); + } else { + echo json_encode(['success' => false, 'message' => 'Incorrect security code. Check the file: .mokorestore-security.php']); + } + + exit; +} + +// Block all other actions until security is verified +$securityVerified = !empty($_SESSION['security_verified']); + // ── AJAX Handler ──────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { header('Content-Type: application/json; charset=utf-8'); @@ -118,6 +164,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { exit; } + if (!$securityVerified) { + echo json_encode(['success' => false, 'message' => 'Security verification required. Enter the code from .mokorestore-security.php']); + exit; + } + @set_time_limit(0); @ini_set('max_execution_time', '0'); @ini_set('memory_limit', '512M'); @@ -348,7 +399,12 @@ function actionDatabase(array $data): array $pdo->exec("SET time_zone = '+00:00'"); $pdo->exec('SET FOREIGN_KEY_CHECKS = 0'); - $sql = file_get_contents($sqlFile); + $sql = file_get_contents($sqlFile); + $prefix = getValidatedPrefix($data); + + // Replace abstract #__ prefix with the user's target prefix + $sql = str_replace('#__', $prefix, $sql); + $parts = explode(";\n", $sql); $statements = 0; $errors = 0; @@ -981,8 +1037,33 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N
To prevent unauthorized access, enter the security code from the file .mokorestore-security.php in your site root.
.mokorestore-security.php in the site root directoryVerify your server meets the requirements for Joomla and MokoRestore.