<?php /** * @package akeebabackup * @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace Akeeba\Component\AkeebaBackup\Administrator\Controller; defined('_JEXEC') || die; use Akeeba\Component\AkeebaBackup\Administrator\Controller\Mixin\ControllerEvents; use Akeeba\Component\AkeebaBackup\Administrator\Controller\Mixin\CustomACL; use Akeeba\Component\AkeebaBackup\Administrator\Controller\Mixin\RegisterControllerTasks; use Akeeba\Component\AkeebaBackup\Administrator\Controller\Mixin\ReusableModels; use Akeeba\Component\AkeebaBackup\Administrator\Model\RemotefilesModel; use Akeeba\Engine\Factory; use Akeeba\Engine\Platform; use Exception; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\Uri\Uri; use Joomla\Input\Input; use RuntimeException; class RemotefilesController extends BaseController { use ControllerEvents; use CustomACL; use RegisterControllerTasks; use ReusableModels; public function __construct($config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null) { parent::__construct($config, $factory, $app, $input); $this->registerControllerTasks('invalidTask'); } public function display($cachable = false, $urlparams = []) { $document = $this->app->getDocument(); $viewType = $document->getType(); $viewName = $this->input->get('view', $this->default_view); $viewLayout = $this->input->get('layout', 'default', 'string'); $view = $this->getView($viewName, $viewType, '', ['base_path' => $this->basePath, 'layout' => $viewLayout]); // Get/Create the model if ($model = $this->getModel($viewName, 'Administrator', ['base_path' => $this->basePath])) { // Push the model into the view (as default) $view->setModel($model, true); } $view->document = $document; $view->task = $this->task; $view->doTask = $this->doTask; // Display the view $view->display(); return $this; } /** * When someone calls this controller without a task we have to show an error message. This is implemented by * having this task throw a runtime exception and set it as the default task. * * @return void * * @noinspection PhpUnused */ public function invalidTask() { throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403); } /** * Lists the available remote storage actions for a specific backup entry * * @return void * @throws Exception */ public function listactions() { // List available actions $id = $this->getAndCheckId(); /** @var RemotefilesModel $model */ $model = $this->getModel('Remotefiles', 'Administrator'); $model->setState('id', $id); if ($id === false) { throw new RuntimeException(Text::_('JGLOBAL_RESOURCE_NOT_FOUND'), 404); } $this->display(false); } /** * Fetches a complete backup set from a remote storage location to the local (server) * storage so that the user can download or restore it. * * @return void * @throws Exception * * @noinspection PhpUnused */ public function dltoserver() { // Get the parameters $id = $this->getAndCheckId(); $part = $this->input->get('part', -1, 'int'); $frag = $this->input->get('frag', -1, 'int'); // Check the ID if ($id === false) { $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, Text::_('COM_AKEEBABACKUP_REMOTEFILES_ERR_INVALIDID'), 'error'); return; } if (($part == -1) && ($frag == -1)) { $this->triggerEvent('onFetch', [$id]); } /** @var RemotefilesModel $model */ $model = $this->getModel('Remotefiles', 'Administrator'); try { $result = $model->downloadToServer($id, $part, $frag); } catch (Exception $e) { $allErrors = $model->getErrorsFromExceptions($e); $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, implode('<br/>', $allErrors), 'error'); return; } if ($result === true) { $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, Text::_('COM_AKEEBABACKUP_REMOTEFILES_LBL_JUSTFINISHED')); return; } $this->display(false); } /** * Downloads a file from the remote storage to the user's browser * * @return void * * @noinspection PhpUnused */ public function dlfromremote() { $id = $this->getAndCheckId(); $part = $this->input->get('part', 0, 'int'); if ($id === false) { $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, Text::_('COM_AKEEBABACKUP_REMOTEFILES_ERR_INVALIDID'), 'error'); return; } $stat = Platform::getInstance()->get_statistics($id); $remoteFilenameParts = explode('://', $stat['remote_filename']); $engine = Factory::getPostprocEngine($remoteFilenameParts[0]); $remote_filename = $remoteFilenameParts[1]; $basename = basename($remote_filename); $extension = strtolower(str_replace(".", "", strrchr($basename, "."))); $new_extension = $extension; if ($part > 0) { $new_extension = substr($extension, 0, 1) . sprintf('%02u', $part); } $this->triggerEvent('onDownload', [$id, $part + 1]); $filename = $basename . '.' . $new_extension; $remote_filename = substr($remote_filename, 0, -strlen($extension)) . $new_extension; if ($engine->doesInlineDownloadToBrowser()) { @ob_end_clean(); @clearstatcache(); // Send MIME headers header('MIME-Version: 1.0'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Transfer-Encoding: binary'); switch ($extension) { case 'zip': // ZIP MIME type header('Content-Type: application/zip'); break; default: // Generic binary data MIME type header('Content-Type: application/octet-stream'); break; } // Disable caching header('Expires: Mon, 20 Dec 1998 01:00:00 GMT'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); } try { $result = $engine->downloadToBrowser($remote_filename); } catch (Exception $e) { $result = null; // Failed to download. Get the messages from the engine. $errors = []; $parentException = $e; while ($parentException) { $errors[] = $e->getMessage(); $parentException = $e->getPrevious(); } // Redirect and convey the errors to the user $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, implode('<br/>', $errors), 'error'); } if (!is_null($result)) { // We have to redirect $result = str_replace('://%2F', '://', $result); @ob_end_clean(); header('Location: ' . $result); flush(); $this->app->close(); } } /** * Deletes a file from the remote storage * * @return void */ public function delete() { // Get the parameters $id = $this->getAndCheckId(); $part = $this->input->get('part', -1, 'int'); // Check the ID if ($id === false) { $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, Text::_('COM_AKEEBABACKUP_REMOTEFILES_ERR_INVALIDID'), 'error'); return; } if ($part == -1) { $this->triggerEvent('onDelete', [$id]); } /** @var RemotefilesModel $model */ $model = $this->getModel('Remotefiles', 'Administrator'); $model->setState('id', $id); $model->setState('part', $part); try { $result = $model->deleteRemoteFiles($id, $part); } catch (Exception $e) { $allErrors = $model->getErrorsFromExceptions($e); $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, implode('<br/>', $allErrors), 'error'); return; } if ($result['finished']) { $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=listactions&id=' . $id; $this->setRedirect($url, Text::_('COM_AKEEBABACKUP_REMOTEFILES_LBL_JUSTFINISHEDELETING')); return; } $url = Uri::base() . 'index.php?option=com_akeebabackup&view=Remotefiles&tmpl=component&task=delete&id=' . $result['id'] . '&part=' . $result['part']; $this->setRedirect($url); } /** * Gets the stats record ID from the request and checks that it does exist * * @return bool|int False if an invalid ID is found, the numeric ID if it's valid */ private function getAndCheckId() { $id = $this->input->get('id', 0, 'int'); if ($id <= 0) { return false; } $backupRecord = Platform::getInstance()->get_statistics($id); if (empty($backupRecord) || !is_array($backupRecord)) { return false; } // Load the correct backup profile. The post-processing engine could rely on the active profile (ie OneDrive). define('AKEEBA_PROFILE', $backupRecord['profile_id']); Platform::getInstance()->load_configuration($backupRecord['profile_id']); return $id; } }