* @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)); } }