chore: rename src/ to source/ per MokoStandards convention
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Update all references in Makefile, manifest.xml, .gitignore, and CI workflows (ci-joomla, pr-check, repo-health) to use source/ as the primary directory with src/ as a fallback for compatibility. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoJoomBackup
|
||||
* @subpackage com_mokobackup
|
||||
* @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\Component\MokoBackup\Administrator\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Component\MokoBackup\Administrator\Engine\BackupEngine;
|
||||
use Joomla\Component\MokoBackup\Administrator\Engine\RestoreEngine;
|
||||
|
||||
class BackupsController extends AdminController
|
||||
{
|
||||
protected $text_prefix = 'COM_MOKOBACKUP_BACKUPS';
|
||||
|
||||
public function getModel($name = 'Backup', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new backup using the specified profile.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start(): void
|
||||
{
|
||||
$this->checkToken();
|
||||
|
||||
$profileId = $this->input->getInt('profile_id', 1);
|
||||
$description = $this->input->getString('description', '');
|
||||
|
||||
$engine = new BackupEngine();
|
||||
$result = $engine->run($profileId, $description, 'backend');
|
||||
|
||||
if ($result['success']) {
|
||||
$this->setMessage($result['message']);
|
||||
} else {
|
||||
$this->setMessage($result['message'], 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a backup archive.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function download(): void
|
||||
{
|
||||
$id = $this->input->getInt('id', 0);
|
||||
$model = $this->getModel('Backup');
|
||||
$item = $model->getItem($id);
|
||||
|
||||
if (!$item || !$item->id || !$item->filesexist || !is_file($item->absolute_path)) {
|
||||
$this->setMessage('COM_MOKOBACKUP_ERROR_FILE_NOT_FOUND', 'error');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush any output buffers to prevent HTML mixing with binary data
|
||||
while (@ob_end_clean()) {
|
||||
// clear all buffers
|
||||
}
|
||||
|
||||
$filename = basename($item->archivename);
|
||||
$filesize = filesize($item->absolute_path);
|
||||
|
||||
// Detect content type from file extension
|
||||
$contentType = str_ends_with($filename, '.tar.gz')
|
||||
? 'application/gzip'
|
||||
: 'application/zip';
|
||||
|
||||
header('Content-Type: ' . $contentType);
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Content-Length: ' . $filesize);
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
readfile($item->absolute_path);
|
||||
|
||||
$this->app->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore from a backup record.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restore(): void
|
||||
{
|
||||
$this->checkToken();
|
||||
|
||||
$id = $this->input->getInt('id', 0);
|
||||
$restoreFiles = (bool) $this->input->getInt('restore_files', 1);
|
||||
$restoreDb = (bool) $this->input->getInt('restore_db', 1);
|
||||
$preserveConfig = (bool) $this->input->getInt('preserve_config', 1);
|
||||
$password = $this->input->getString('encryption_password', '');
|
||||
|
||||
if (!$id) {
|
||||
$this->setMessage('COM_MOKOBACKUP_ERROR_NO_RECORD_SELECTED', 'error');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$engine = new RestoreEngine();
|
||||
$result = $engine->restore($id, $restoreFiles, $restoreDb, $preserveConfig, $password);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->setMessage($result['message']);
|
||||
} else {
|
||||
$this->setMessage($result['message'], 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify integrity of a backup archive by re-computing SHA-256.
|
||||
*/
|
||||
public function verify(): void
|
||||
{
|
||||
$this->checkToken();
|
||||
|
||||
$cid = $this->input->get('cid', [], 'array');
|
||||
$id = !empty($cid) ? (int) $cid[0] : $this->input->getInt('id', 0);
|
||||
|
||||
if (!$id) {
|
||||
$this->setMessage('COM_MOKOBACKUP_ERROR_NO_RECORD_SELECTED', 'error');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$model = $this->getModel('Backup');
|
||||
$item = $model->getItem($id);
|
||||
|
||||
if (!$item || !$item->id) {
|
||||
$this->setMessage('COM_MOKOBACKUP_ERROR_NO_RECORD_SELECTED', 'error');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_file($item->absolute_path)) {
|
||||
$this->setMessage('COM_MOKOBACKUP_ERROR_FILE_NOT_FOUND', 'error');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($item->checksum)) {
|
||||
$this->setMessage('COM_MOKOBACKUP_VERIFY_NO_CHECKSUM', 'warning');
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$currentHash = hash_file('sha256', $item->absolute_path);
|
||||
|
||||
if ($currentHash === $item->checksum) {
|
||||
$this->setMessage('COM_MOKOBACKUP_VERIFY_OK');
|
||||
} else {
|
||||
$this->setMessage('COM_MOKOBACKUP_VERIFY_FAILED', 'error');
|
||||
}
|
||||
|
||||
$this->setRedirect(Route::_('index.php?option=com_mokobackup&view=backups', false));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user