* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @license GNU General Public License version 3 or later; see LICENSE */ namespace Joomla\Component\MokoSuiteBackup\Administrator\Controller; defined('_JEXEC') or die; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\AdminController; use Joomla\CMS\Router\Route; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RestoreEngine; class BackupsController extends AdminController { protected $text_prefix = 'COM_MOKOJOOMBACKUP_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(); if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.run', 'com_mokosuitebackup')) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $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_mokosuitebackup&view=backups', false)); } /** * Download a backup archive. * * @return void */ public function download(): void { $this->checkToken('get'); if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.download', 'com_mokosuitebackup')) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $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(Text::_('COM_MOKOJOOMBACKUP_ERROR_FILE_NOT_FOUND'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&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*=UTF-8''" . rawurlencode($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(); if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.restore', 'com_mokosuitebackup')) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $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(Text::_('COM_MOKOJOOMBACKUP_ERROR_NO_RECORD_SELECTED'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&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_mokosuitebackup&view=backups', false)); } /** * Verify integrity of a backup archive by re-computing SHA-256. */ public function verify(): void { $this->checkToken(); if (!$this->app->getIdentity()->authorise('core.manage', 'com_mokosuitebackup')) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $cid = $this->input->get('cid', [], 'array'); $id = !empty($cid) ? (int) $cid[0] : $this->input->getInt('id', 0); if (!$id) { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_ERROR_NO_RECORD_SELECTED'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $model = $this->getModel('Backup'); $item = $model->getItem($id); if (!$item || !$item->id) { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_ERROR_NO_RECORD_SELECTED'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } if (!is_file($item->absolute_path)) { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_ERROR_FILE_NOT_FOUND'), 'error'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } if (empty($item->checksum)) { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_VERIFY_NO_CHECKSUM'), 'warning'); $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); return; } $currentHash = hash_file('sha256', $item->absolute_path); if ($currentHash === $item->checksum) { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_VERIFY_OK')); } else { $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_VERIFY_FAILED'), 'error'); } $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false)); } }