|
|
|
@@ -165,7 +165,38 @@ SCANNER;
|
|
|
|
|
$php
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Modify the pre-checks to use getSelectedBackupFile() */
|
|
|
|
|
/* Replace the backup archive check with one that scans for ZIPs
|
|
|
|
|
(must run BEFORE the blanket file_exists replacement below) */
|
|
|
|
|
$php = str_replace(
|
|
|
|
|
<<<'ORIG'
|
|
|
|
|
$checks[] = [
|
|
|
|
|
'label' => 'Backup Archive',
|
|
|
|
|
'value' => file_exists(BACKUP_FILE) ? number_format(filesize(BACKUP_FILE) / 1048576, 2) . ' MB' : 'Not found',
|
|
|
|
|
'ok' => file_exists(BACKUP_FILE),
|
|
|
|
|
'hint' => 'site-backup.zip must be in the same directory as restore.php',
|
|
|
|
|
];
|
|
|
|
|
ORIG,
|
|
|
|
|
<<<'REPL'
|
|
|
|
|
$availableBackups = scanForBackups();
|
|
|
|
|
$backupCount = count($availableBackups);
|
|
|
|
|
$selectedFile = getSelectedBackupFile();
|
|
|
|
|
if ($selectedFile && file_exists($selectedFile)) {
|
|
|
|
|
$archiveValue = basename($selectedFile) . ' (' . number_format(filesize($selectedFile) / 1048576, 2) . ' MB)';
|
|
|
|
|
} elseif ($backupCount > 0) {
|
|
|
|
|
$archiveValue = $backupCount . ' ZIP file(s) found';
|
|
|
|
|
} else {
|
|
|
|
|
$archiveValue = 'No ZIP files found';
|
|
|
|
|
}
|
|
|
|
|
$checks[] = [
|
|
|
|
|
'label' => 'Backup Archive',
|
|
|
|
|
'value' => $archiveValue,
|
|
|
|
|
'ok' => $backupCount > 0,
|
|
|
|
|
'hint' => 'Place one or more backup ZIP files in the same directory as restore.php',
|
|
|
|
|
];
|
|
|
|
|
REPL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Modify remaining pre-checks to use getSelectedBackupFile() */
|
|
|
|
|
$php = str_replace(
|
|
|
|
|
"file_exists(BACKUP_FILE)",
|
|
|
|
|
"(getSelectedBackupFile() !== '' || file_exists(BACKUP_FILE))",
|
|
|
|
@@ -174,65 +205,83 @@ SCANNER;
|
|
|
|
|
|
|
|
|
|
$html = self::generateFrontend();
|
|
|
|
|
|
|
|
|
|
/* Add backup file selector to the frontend before the extract step */
|
|
|
|
|
/* Inject backup file selector into the extract step (panel2) */
|
|
|
|
|
$selectorHtml = <<<'SELECTOR'
|
|
|
|
|
<!-- Backup File Selector (standalone mode) -->
|
|
|
|
|
<div id="mr-step-select" class="mr-step" style="display:none;">
|
|
|
|
|
<h2 class="mr-step-title">Select Backup File</h2>
|
|
|
|
|
<p class="mr-desc">Choose which backup archive to restore from.</p>
|
|
|
|
|
<div id="mr-backup-list"></div>
|
|
|
|
|
<input type="hidden" name="backup_file" id="mr-backup-file" value="">
|
|
|
|
|
</div>
|
|
|
|
|
<script>
|
|
|
|
|
(function() {
|
|
|
|
|
var backups = <?php echo json_encode(scanForBackups()); ?>;
|
|
|
|
|
var list = document.getElementById('mr-backup-list');
|
|
|
|
|
var hiddenInput = document.getElementById('mr-backup-file');
|
|
|
|
|
<div id="mr-backup-selector" class="mb-3">
|
|
|
|
|
<label class="mr-field-label" style="font-weight:600;margin-bottom:8px;display:block;">Backup Archive</label>
|
|
|
|
|
<div id="mr-backup-list"></div>
|
|
|
|
|
<input type="hidden" name="backup_file" id="mr-backup-file" value="">
|
|
|
|
|
</div>
|
|
|
|
|
<script>
|
|
|
|
|
(function() {
|
|
|
|
|
var backups = <?php echo json_encode(scanForBackups()); ?>;
|
|
|
|
|
var list = document.getElementById('mr-backup-list');
|
|
|
|
|
var hiddenInput = document.getElementById('mr-backup-file');
|
|
|
|
|
|
|
|
|
|
if (backups.length === 0) {
|
|
|
|
|
var alert = document.createElement('div');
|
|
|
|
|
alert.className = 'mr-alert mr-alert-danger';
|
|
|
|
|
alert.textContent = 'No ZIP files found in this directory. Upload a backup archive first.';
|
|
|
|
|
list.appendChild(alert);
|
|
|
|
|
} else if (backups.length === 1) {
|
|
|
|
|
hiddenInput.value = backups[0].name;
|
|
|
|
|
var found = document.createElement('div');
|
|
|
|
|
found.className = 'mr-alert mr-alert-success';
|
|
|
|
|
var strong = document.createElement('strong');
|
|
|
|
|
strong.textContent = backups[0].name;
|
|
|
|
|
found.appendChild(document.createTextNode('Found: '));
|
|
|
|
|
found.appendChild(strong);
|
|
|
|
|
found.appendChild(document.createTextNode(' (' + (backups[0].size / 1048576).toFixed(1) + ' MB)'));
|
|
|
|
|
list.appendChild(found);
|
|
|
|
|
} else {
|
|
|
|
|
var group = document.createElement('div');
|
|
|
|
|
group.className = 'mr-field-group';
|
|
|
|
|
backups.forEach(function(b) {
|
|
|
|
|
var label = document.createElement('label');
|
|
|
|
|
label.style.cssText = 'display:block; padding:8px; margin:4px 0; border:1px solid #ddd; border-radius:4px; cursor:pointer;';
|
|
|
|
|
var radio = document.createElement('input');
|
|
|
|
|
radio.type = 'radio';
|
|
|
|
|
radio.name = 'backup_choice';
|
|
|
|
|
radio.value = b.name;
|
|
|
|
|
radio.style.marginRight = '8px';
|
|
|
|
|
radio.addEventListener('change', function() { hiddenInput.value = this.value; });
|
|
|
|
|
label.appendChild(radio);
|
|
|
|
|
var nameStrong = document.createElement('strong');
|
|
|
|
|
nameStrong.textContent = b.name;
|
|
|
|
|
label.appendChild(nameStrong);
|
|
|
|
|
label.appendChild(document.createTextNode(' \u2014 ' + (b.size / 1048576).toFixed(1) + ' MB \u2014 ' + b.date));
|
|
|
|
|
group.appendChild(label);
|
|
|
|
|
});
|
|
|
|
|
list.appendChild(group);
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
if (backups.length === 0) {
|
|
|
|
|
var alert = document.createElement('div');
|
|
|
|
|
alert.style.cssText = 'padding:12px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626;';
|
|
|
|
|
alert.textContent = 'No ZIP files found in this directory. Upload a backup archive first.';
|
|
|
|
|
list.appendChild(alert);
|
|
|
|
|
} else if (backups.length === 1) {
|
|
|
|
|
hiddenInput.value = backups[0].name;
|
|
|
|
|
var found = document.createElement('div');
|
|
|
|
|
found.style.cssText = 'padding:12px;background:#dcfce7;border:1px solid #bbf7d0;border-radius:6px;color:#16a34a;';
|
|
|
|
|
var strong = document.createElement('strong');
|
|
|
|
|
strong.textContent = backups[0].name;
|
|
|
|
|
found.appendChild(document.createTextNode('Found: '));
|
|
|
|
|
found.appendChild(strong);
|
|
|
|
|
found.appendChild(document.createTextNode(' (' + (backups[0].size / 1048576).toFixed(1) + ' MB)'));
|
|
|
|
|
list.appendChild(found);
|
|
|
|
|
} else {
|
|
|
|
|
var hint = document.createElement('div');
|
|
|
|
|
hint.style.cssText = 'padding:8px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;color:#1d4ed8;margin-bottom:8px;font-size:0.9em;';
|
|
|
|
|
hint.textContent = 'Multiple backup archives found \u2014 select which one to restore:';
|
|
|
|
|
list.appendChild(hint);
|
|
|
|
|
backups.forEach(function(b, i) {
|
|
|
|
|
var label = document.createElement('label');
|
|
|
|
|
label.style.cssText = 'display:flex;align-items:center;padding:10px 12px;margin:4px 0;border:1px solid #e2e8f0;border-radius:6px;cursor:pointer;transition:background 0.15s;';
|
|
|
|
|
label.onmouseover = function() { this.style.background = '#f8fafc'; };
|
|
|
|
|
label.onmouseout = function() { this.style.background = ''; };
|
|
|
|
|
var radio = document.createElement('input');
|
|
|
|
|
radio.type = 'radio';
|
|
|
|
|
radio.name = 'backup_choice';
|
|
|
|
|
radio.value = b.name;
|
|
|
|
|
radio.style.marginRight = '10px';
|
|
|
|
|
if (i === 0) { radio.checked = true; hiddenInput.value = b.name; }
|
|
|
|
|
radio.addEventListener('change', function() { hiddenInput.value = this.value; });
|
|
|
|
|
label.appendChild(radio);
|
|
|
|
|
var info = document.createElement('div');
|
|
|
|
|
var nameStrong = document.createElement('strong');
|
|
|
|
|
nameStrong.textContent = b.name;
|
|
|
|
|
info.appendChild(nameStrong);
|
|
|
|
|
var meta = document.createElement('div');
|
|
|
|
|
meta.style.cssText = 'font-size:0.85em;color:#64748b;margin-top:2px;';
|
|
|
|
|
meta.textContent = (b.size / 1048576).toFixed(1) + ' MB \u2014 ' + b.date;
|
|
|
|
|
info.appendChild(meta);
|
|
|
|
|
label.appendChild(info);
|
|
|
|
|
list.appendChild(label);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
SELECTOR;
|
|
|
|
|
|
|
|
|
|
/* Insert the selector before the extract step in the HTML */
|
|
|
|
|
/* Insert the selector into the extract panel */
|
|
|
|
|
$html = str_replace(
|
|
|
|
|
'<!-- Step: Extract -->',
|
|
|
|
|
$selectorHtml . "\n<!-- Step: Extract -->",
|
|
|
|
|
'<p class="mr-desc">Extract site-backup.zip into the current directory.</p>',
|
|
|
|
|
'<p class="mr-desc">Select a backup archive and extract it into the current directory.</p>' . "\n" . $selectorHtml,
|
|
|
|
|
$html
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Pass selected backup file to the extract action */
|
|
|
|
|
$html = str_replace(
|
|
|
|
|
"const r = await post('extract', pw ? { archive_password: pw } : {});",
|
|
|
|
|
"var extraParams = {};\n" .
|
|
|
|
|
" if (pw) extraParams.archive_password = pw;\n" .
|
|
|
|
|
" var sel = document.getElementById('mr-backup-file');\n" .
|
|
|
|
|
" if (sel && sel.value) extraParams.backup_file = sel.value;\n" .
|
|
|
|
|
" const r = await post('extract', extraParams);",
|
|
|
|
|
$html
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
@@ -462,15 +511,31 @@ function actionPreflight(): array
|
|
|
|
|
'hint' => 'Informational',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$joomlaExists = file_exists(RESTORE_DIR . '/configuration.php')
|
|
|
|
|
|| file_exists(RESTORE_DIR . '/libraries/src/Version.php');
|
|
|
|
|
$checks[] = [
|
|
|
|
|
'label' => 'Existing Installation',
|
|
|
|
|
'value' => $joomlaExists ? 'Joomla detected' : 'Clean directory',
|
|
|
|
|
'ok' => true,
|
|
|
|
|
'warn' => $joomlaExists,
|
|
|
|
|
'hint' => $joomlaExists
|
|
|
|
|
? 'WARNING: A Joomla installation already exists in this directory. Restoring will overwrite it.'
|
|
|
|
|
: 'No existing installation found — safe to proceed',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$allOk = true;
|
|
|
|
|
$warnings = [];
|
|
|
|
|
|
|
|
|
|
foreach ($checks as $c) {
|
|
|
|
|
if (!$c['ok']) {
|
|
|
|
|
$allOk = false;
|
|
|
|
|
}
|
|
|
|
|
if (!empty($c['warn'])) {
|
|
|
|
|
$warnings[] = $c['hint'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ['success' => $allOk, 'checks' => $checks];
|
|
|
|
|
return ['success' => $allOk, 'checks' => $checks, 'warnings' => $warnings];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function actionExtract(array $data): array
|
|
|
|
@@ -1425,6 +1490,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N
|
|
|
|
|
.mr-checks li:last-child{border-bottom:none}
|
|
|
|
|
.mr-check-icon{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;flex-shrink:0}
|
|
|
|
|
.mr-check-ok{background:#dcfce7;color:#16a34a}
|
|
|
|
|
.mr-check-warn{background:#fef9c3;color:#a16207}
|
|
|
|
|
.mr-check-fail{background:#fef2f2;color:#dc2626}
|
|
|
|
|
.mr-check-info{background:#e0f2fe;color:#0284c7}
|
|
|
|
|
.mr-check-label{flex:1;font-weight:500}
|
|
|
|
@@ -1877,8 +1943,10 @@ async function runPreflight() {
|
|
|
|
|
r.checks.forEach(function(c) {
|
|
|
|
|
const li = document.createElement('li');
|
|
|
|
|
const icon = document.createElement('span');
|
|
|
|
|
icon.className = 'mr-check-icon ' + (c.ok ? 'mr-check-ok' : 'mr-check-fail');
|
|
|
|
|
icon.textContent = c.ok ? '\u2713' : '\u2717';
|
|
|
|
|
var iconClass = c.ok ? 'mr-check-ok' : 'mr-check-fail';
|
|
|
|
|
if (c.warn) iconClass = 'mr-check-warn';
|
|
|
|
|
icon.className = 'mr-check-icon ' + iconClass;
|
|
|
|
|
icon.textContent = c.warn ? '\u26a0' : (c.ok ? '\u2713' : '\u2717');
|
|
|
|
|
|
|
|
|
|
const label = document.createElement('span');
|
|
|
|
|
label.className = 'mr-check-label';
|
|
|
|
@@ -1891,9 +1959,16 @@ async function runPreflight() {
|
|
|
|
|
li.appendChild(icon);
|
|
|
|
|
li.appendChild(label);
|
|
|
|
|
li.appendChild(val);
|
|
|
|
|
if (c.warn && c.hint) {
|
|
|
|
|
var hint = document.createElement('div');
|
|
|
|
|
hint.style.cssText = 'font-size:0.85em;color:#a16207;margin-top:4px;padding:4px 8px;background:#fef9c3;border-radius:4px;';
|
|
|
|
|
hint.textContent = c.hint;
|
|
|
|
|
li.appendChild(hint);
|
|
|
|
|
}
|
|
|
|
|
list.appendChild(li);
|
|
|
|
|
|
|
|
|
|
log(' ' + (c.ok ? 'OK' : 'FAIL') + ': ' + c.label + ' = ' + c.value);
|
|
|
|
|
var logPrefix = c.warn ? 'WARN' : (c.ok ? 'OK' : 'FAIL');
|
|
|
|
|
log(' ' + logPrefix + ': ' + c.label + ' = ' + c.value);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setBtnLoading(btn, false);
|
|
|
|
|