f66100f74f
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 13s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 34s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 3s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 27s
SFTP support: - SftpUploader uses system scp/ssh binaries with key file auth - Private key stored as MEDIUMTEXT in profile table (sftp_key_data) - Key written to temp file (0600) at upload time, deleted after - Profile form: host, port, username, password, key textarea, passphrase, remote path — all with showon="remote_storage:sftp" - SQL migration for 7 new SFTP columns - Wired into BackupEngine, SteppedBackupEngine, PreflightCheck - API credential masking includes SFTP fields CLI restore options: - --files-only: restore files without touching database - --db-only: restore database without touching files - --no-preserve-config: overwrite configuration.php - --password: decryption password for encrypted archives
121 lines
3.7 KiB
PHP
121 lines
3.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoSuiteBackup
|
|
* @subpackage plg_console_mokosuitebackup
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Joomla\Plugin\Console\MokoSuiteBackup\Command;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RestoreEngine;
|
|
use Joomla\Console\Command\AbstractCommand;
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
|
|
class RestoreCommand extends AbstractCommand
|
|
{
|
|
protected static $defaultName = 'mokosuitebackup:restore';
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Restore a backup by record ID');
|
|
$this->addArgument('id', InputArgument::REQUIRED, 'Backup record ID to restore');
|
|
$this->addOption('files-only', null, InputOption::VALUE_NONE, 'Restore files only (skip database)');
|
|
$this->addOption('db-only', null, InputOption::VALUE_NONE, 'Restore database only (skip files)');
|
|
$this->addOption('no-preserve-config', null, InputOption::VALUE_NONE, 'Do not preserve current configuration.php');
|
|
$this->addOption('password', 'p', InputOption::VALUE_REQUIRED, 'Decryption password for encrypted archives', '');
|
|
}
|
|
|
|
protected function doExecute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
$io = new SymfonyStyle($input, $output);
|
|
$recordId = (int) $input->getArgument('id');
|
|
|
|
$io->title('MokoSuiteBackup — Restore Backup');
|
|
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
->where($db->quoteName('id') . ' = ' . $recordId);
|
|
$db->setQuery($query);
|
|
$record = $db->loadObject();
|
|
|
|
if (!$record) {
|
|
$io->error('Backup record not found: ' . $recordId);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if ($record->status !== 'complete') {
|
|
$io->error('Cannot restore — backup status is: ' . $record->status);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (empty($record->absolute_path) || !is_file($record->absolute_path)) {
|
|
$io->error('Backup archive not found: ' . ($record->absolute_path ?: 'no path'));
|
|
|
|
return 1;
|
|
}
|
|
|
|
$io->warning('This will overwrite the current site files and/or database.');
|
|
$io->text('Archive: ' . $record->absolute_path);
|
|
$io->text('Type: ' . $record->backup_type);
|
|
|
|
if (!$io->confirm('Are you sure you want to continue?', false)) {
|
|
$io->info('Restore cancelled.');
|
|
|
|
return 0;
|
|
}
|
|
|
|
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Engine/RestoreEngine.php';
|
|
|
|
if (!file_exists($engineFile)) {
|
|
$io->error('RestoreEngine not found. Is the component fully installed?');
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (!class_exists(RestoreEngine::class)) {
|
|
require_once $engineFile;
|
|
}
|
|
|
|
$filesOnly = $input->getOption('files-only');
|
|
$dbOnly = $input->getOption('db-only');
|
|
$preserveConfig = !$input->getOption('no-preserve-config');
|
|
$password = $input->getOption('password') ?: '';
|
|
|
|
$restoreFiles = !$dbOnly;
|
|
$restoreDb = !$filesOnly;
|
|
|
|
if ($filesOnly) {
|
|
$io->note('Restoring files only (database will not be touched)');
|
|
} elseif ($dbOnly) {
|
|
$io->note('Restoring database only (files will not be touched)');
|
|
}
|
|
|
|
$engine = new RestoreEngine();
|
|
$result = $engine->restore($recordId, $restoreFiles, $restoreDb, $preserveConfig, $password);
|
|
|
|
if ($result['success']) {
|
|
$io->success($result['message']);
|
|
|
|
return 0;
|
|
}
|
|
|
|
$io->error($result['message']);
|
|
|
|
return 1;
|
|
}
|
|
}
|