shell bypass 403
<?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Component\AkeebaBackup\Administrator\View\Manage;
defined('_JEXEC') || die;
use Akeeba\Component\AkeebaBackup\Administrator\Mixin\ViewBackupStartTimeTrait;
use Akeeba\Component\AkeebaBackup\Administrator\Mixin\ViewLoadAnyTemplateTrait;
use Akeeba\Component\AkeebaBackup\Administrator\Model\ProfilesModel;
use Akeeba\Component\AkeebaBackup\Administrator\Model\StatisticsModel;
use Akeeba\Engine\Factory as AkeebaFactory;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text as Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Uri\Uri as JUri;
use Joomla\Registry\Registry;
#[\AllowDynamicProperties]
class HtmlView extends BaseHtmlView
{
use ViewLoadAnyTemplateTrait;
use ViewBackupStartTimeTrait;
/**
* The active search filters
*
* @since 1.6
* @var array
*/
public $activeFilters = [];
/**
* The search tools form
*
* @since 1.6
* @var Form
*/
public $filterForm;
/**
* List of frozen options for JHtmlSelect
*
* @var array
*/
public $frozenList = [];
/**
* List of records to display
*
* @var array
*/
public $items = [];
/**
* Order direction, ASC/DESC
*
* @var string
*/
public $order_Dir = 'DESC';
/**
* Pagination object
*
* @var Pagination
*/
public $pagination = null;
/**
* Cache the user permissions
*
* @since 5.3.0
* @var array
*
*/
public $permissions = [];
/**
* List of Profiles objects
*
* @var array
*/
public $profiles = [];
/**
* List of profiles for JHtmlSelect
*
* @var array
*/
public $profilesList = [];
/**
* Should I pormpt the user ot run the configuration wizard?
*
* @var bool
*/
public $promptForBackupRestoration = false;
/**
* Sorting order options
*
* @var array
*/
public $sortFields = [];
/**
* @var array
*/
protected $enginesPerProfile;
/**
* The model state
*
* @since 1.6
* @var Registry
*/
protected $state;
/**
* @since 9.4.2
* @var int|null
*/
private ?int $itemCount;
public function display($tpl = null)
{
// Load custom Javascript for this page
$this->document->getWebAssetManager()
->useScript('com_akeebabackup.manage');
$cParams = ComponentHelper::getParams('com_akeebabackup');
$app = Factory::getApplication();
$user = $app->getIdentity();
$this->permissions = [
'configure' => $user->authorise('akeebabackup.configure', 'com_akeebabackup'),
'backup' => $user->authorise('akeebabackup.backup', 'com_akeebabackup'),
'download' => $user->authorise('akeebabackup.download', 'com_akeebabackup'),
];
// Push translations to the frontend
Text::script('COM_AKEEBABACKUP_BUADMIN_LABEL_REMOTEFILEMGMT');
Text::script('COM_AKEEBABACKUP_REMOTEFILES_INPROGRESS_HEADER');
/** @var ProfilesModel $profilesModel */
$profilesModel = $this->getModel('Profiles');
$this->enginesPerProfile = $profilesModel->getPostProcessingEnginePerProfile();
// "Show warning first" download button.
Text::script('COM_AKEEBABACKUP_BUADMIN_LOG_DOWNLOAD_CONFIRM', false);
$this->document
->addScriptOptions('akeebabackup.Manage.baseURI', JUri::base())
->addScriptOptions('akeebabackup.Manage.downloadURL', Route::_('index.php?option=com_akeebabackup&task=Manage.download&' . $app->getSession()->getFormToken() . '=1', false));
/** @var StatisticsModel $model */
$model = $this->getModel('Statistics');
$filters = $this->getFilters();
$ordering = $this->getOrdering();
$this->state = $model->getState();
$this->filterForm = $model->getFilterForm();
$this->activeFilters = $model->getActiveFilters();
$this->items = $model->getStatisticsListWithMeta(false, $filters, $ordering);
// Let's create an array indexed with the profile id for better handling
$profilesModel->setState('filter.search', '');
$profilesModel->setState('filter.quickicon', '');
$profilesModel->setState('filter.quickicon', '');
$profilesModel->setState('list.start', 0);
$profilesModel->setState('list.limit', 0);
$tempProfiles = $profilesModel->getItems();
$profiles = [];
foreach ($tempProfiles as $profile)
{
$profiles[$profile->id] = $profile;
}
$profilesList = [
HTMLHelper::_('select.option', '', '–' . Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_PROFILEID') . '–'),
];
if (!empty($profiles))
{
foreach ($profiles as $profile)
{
$profilesList[] = HTMLHelper::_('select.option', $profile->id, '#' . $profile->id . '. ' . $profile->description);
}
}
// Assign data to the view
$this->profiles = $profiles; // Profiles
$this->profilesList = $profilesList; // Profiles list for select box
$this->itemCount = count($this->items);
$this->pagination = $model->getFilteredPagination($filters); // Pagination object
$this->frozenList = [
HTMLHelper::_('select.option', '', '–' . Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_FROZEN_SELECT') . '–'),
HTMLHelper::_('select.option', '1', Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_FROZEN_FROZEN')),
HTMLHelper::_('select.option', '2', Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_FROZEN_UNFROZEN')),
];
// Initialise the timezone information and preferences about displaying the backup start time
$this->initTimeInformation();
// Should I show the prompt for the configuration wizard?
$this->promptForBackupRestoration = $cParams->get('show_howtorestoremodal', 1) != 0;
// Construct the array of sorting fields
$this->sortFields = [
'id' => Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_ID'),
'description' => Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_DESCRIPTION'),
'backupstart' => Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_START'),
'profile_id' => Text::_('COM_AKEEBABACKUP_BUADMIN_LABEL_PROFILEID'),
];
$this->addToolbar();
parent::display($tpl);
}
/**
* File size formatting function. COnverts number of bytes to a human readable represenation.
*
* @param int $sizeInBytes Size in bytes
* @param int $decimals How many decimals should I use? Default: 2
* @param string $decSeparator Decimal separator
* @param string $thousandsSeparator Thousands grouping character
*
* @return string
*/
public function formatFilesize($sizeInBytes, $decimals = 2, $decSeparator = '.', $thousandsSeparator = '')
{
if ($sizeInBytes <= 0)
{
return '-';
}
$units = ['b', 'KB', 'MB', 'GB', 'TB'];
$unit = floor(log($sizeInBytes, 2) / 10);
if ($unit == 0)
{
$decimals = 0;
}
return number_format($sizeInBytes / (1024 ** $unit), $decimals, $decSeparator, $thousandsSeparator) . ' ' . $units[$unit];
}
/**
* Returns the custom states for frozen/unfrozen records, for use with JGrid.state
*
* @return array[]
* @since 9.0.0
*/
public function getFrozenStates()
{
return [
// Frozen record
1 => [
// Default toggle action is to unpublish (unfreeze) the record.
'task' => 'unpublish',
// Ignored
'text' => '',
// The tooltip reads "Unfreeze record"
'active_title' => 'COM_AKEEBABACKUP_BUADMIN_LABEL_ACTION_UNFREEZE',
// Ignored (we don't do disabled state toggles in Akeeba Backup)
'inactive_title' => '',
// Show a tooltip, please
'tip' => true,
// The x in the beginning prevents Joomla rendering its own icons. The rest is self-explanatory.
'active_class' => 'x text-primary border-primary fa fa-snowflake akeebabackup-icon-frozen',
// Ignored (we don't do disabled state toggles in Akeeba Backup)
'inactive_class' => 'x text-primary border-primary fa fa-snowflake akeebabackup-icon-frozen',
],
// Unfrozen record (DEFAULT STATE)
0 => [
// Default toggle action is to publish (freeze) the record.
'task' => 'publish',
// Ignored
'text' => '',
// The tooltip reads "Freeze record"
'active_title' => 'COM_AKEEBABACKUP_BUADMIN_LABEL_ACTION_FREEZE',
// Ignored (we don't do disabled state toggles in Akeeba Backup)
'inactive_title' => '',
// Show a tooltip, please
'tip' => true,
// The x in the beginning prevents Joomla rendering its own icons. The rest is self-explanatory.
'active_class' => 'x fa fa-tint',
// Ignored (we don't do disabled state toggles in Akeeba Backup)
'inactive_class' => 'x fa fa-tint',
],
];
}
/**
* Translates the internal backup type (e.g. cli) to a human readable string
*
* @param string $recordType The internal backup type
*
* @return string
*/
public function translateBackupType($recordType)
{
static $backup_types = null;
if (!is_array($backup_types))
{
// Load a mapping of backup types to textual representation
$scripting = AkeebaFactory::getEngineParamsProvider()->loadScripting();
$backup_types = [];
foreach ($scripting['scripts'] as $key => $data)
{
$textKey = str_replace('COM_AKEEBA_', 'COM_AKEEBABACKUP_', $data['text']);
$backup_types[$key] = Text::_($textKey);
}
}
if (array_key_exists($recordType, $backup_types))
{
return $backup_types[$recordType];
}
return '–';
}
/**
* Escapes backup comment to remove all tags, convert new-lines and finally convert HTML entities
*
* @param $comment
*
* @return string
*/
protected function escapeComment($comment)
{
if (!$comment)
{
return '';
}
$comment = strip_tags($comment);
$comment = nl2br($comment);
return $this->escape($comment);
}
/**
* Returns the origin's translated name and the appropriate icon class
*
* @param array $record A backup record
*
* @return array array(originTranslation, iconClass)
*/
protected function getOriginInformation($record)
{
$originLanguageKey = 'COM_AKEEBABACKUP_BUADMIN_LABEL_ORIGIN_' . $record['origin'];
$originDescription = Text::_($originLanguageKey);
switch (strtolower($record['origin']))
{
case 'backend':
$originIcon = 'fa fa-desktop';
break;
case 'frontend':
$originIcon = 'fa fa-globe';
break;
case 'json':
$originIcon = 'fa fa-cloud';
break;
case 'joomlacli':
case 'joomla':
$originIcon = 'fa fab fa-joomla';
break;
case 'cli':
$originIcon = 'fa fa-terminal';
break;
case 'xmlrpc':
$originIcon = 'fa fa-code';
break;
case 'lazy':
$originIcon = 'fa fa-cubes';
break;
default:
$originIcon = 'fa fa-question';
break;
}
if (empty($originLanguageKey) || ($originDescription == $originLanguageKey))
{
$originDescription = '–';
$originIcon = 'fa fa-question-circle';
return [$originDescription, $originIcon];
}
return [$originDescription, $originIcon];
}
/**
* Get the profile name for the backup record (or "–" if the profile no longer exists)
*
* @param array $record A backup record
*
* @return string
*/
protected function getProfileName($record)
{
$profileName = '—';
if (isset($this->profiles[$record['profile_id']]))
{
$profileName = $this->escape($this->profiles[$record['profile_id']]->description);
return $profileName;
}
return $profileName;
}
/**
* Get the class and icon for the backup status indicator
*
* @param array $record A backup record
*
* @return array array(class, icon)
*/
protected function getStatusInformation($record)
{
$statusClass = '';
switch ($record['meta'])
{
case 'ok':
$statusIcon = 'fa fa-check-circle';
$statusClass = 'bg-success';
break;
case 'pending':
$statusIcon = 'fa fa-play';
$statusClass = 'bg-warning';
break;
case 'fail':
$statusIcon = 'fa fa-times';
$statusClass = 'bg-danger';
break;
case 'remote':
$statusIcon = 'fa fa-cloud';
$statusClass = 'bg-primary';
break;
default:
$statusIcon = 'fa fa-trash';
$statusClass = 'bg-secondary';
break;
}
return [$statusClass, $statusIcon];
}
private function addToolbar(): void
{
$user = Factory::getUser();
$permissions = [
'configure' => $user->authorise('akeebabackup.configure', 'com_akeebabackup'),
'backup' => $user->authorise('akeebabackup.backup', 'com_akeebabackup'),
];
// Get the toolbar object instance
$toolbar = Toolbar::getInstance('toolbar');
ToolbarHelper::title(Text::_('COM_AKEEBABACKUP_BUADMIN'), 'icon-akeeba');
if (AKEEBABACKUP_PRO)
{
$toolbar->linkButton('discover', 'COM_AKEEBABACKUP_DISCOVER')
->url(Uri::base() . 'index.php?option=com_akeebabackup&view=Discover')
->icon('fa fa-file-import');
}
if ($permissions['configure'])
{
$dropdown = $toolbar->dropdownButton('status-group')
->text('JTOOLBAR_CHANGE_STATUS')
->toggleSplit(false)
->icon('icon-ellipsis-h')
->buttonClass('btn btn-action')
->listCheck(true);
/** @var Toolbar $childBar */
$childBar = $dropdown->getChildToolbar();
$childBar->publish('manage.publish')
->icon('fa fa-check-circle')
->text('COM_AKEEBABACKUP_BUADMIN_LABEL_ACTION_FREEZE')
->listCheck(true);
$childBar->unpublish('manage.unpublish')
->icon('fa fa-times-circle')
->text('COM_AKEEBABACKUP_BUADMIN_LABEL_ACTION_UNFREEZE')
->listCheck(true);
if ($permissions['configure'] && AKEEBABACKUP_PRO)
{
$childBar->standardButton('restore', 'COM_AKEEBABACKUP_BUADMIN_LABEL_RESTORE', 'restore.main')
->buttonClass('bg-warning')
->icon('fa fa-history')
->listCheck(true);
}
$childBar->delete('manage.deletefiles')
->icon('fa fa-broom')
->text('COM_AKEEBABACKUP_BUADMIN_LABEL_DELETEFILES')
->message('JGLOBAL_CONFIRM_DELETE')
->listCheck(true);
$childBar->delete('manage.delete')
->message('JGLOBAL_CONFIRM_DELETE')
->listCheck(true);
}
if ($permissions['backup'])
{
$toolbar->edit('statistic.edit');
}
$toolbar->back()
->text('COM_AKEEBABACKUP_CONTROLPANEL')
->icon('fa fa-' . (Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left'))
->url('index.php?option=com_akeebabackup');
$toolbar->help(null, false, 'https://www.akeeba.com/documentation/akeeba-backup-joomla/adminsiter-backup-files.html');
}
/**
* Get the filters in a format that Akeeba Engine understands
*
* @return array
*/
private function getFilters()
{
$filters = [];
$model = $this->getModel('Statistics');
if ($model->getState('filter.search'))
{
$filters[] = [
'field' => 'description',
'operand' => 'LIKE',
'value' => $model->getState('filter.search'),
];
}
$from = $model->getState('filter.from');
$to = $model->getState('filter.to');
if ($from && $to)
{
$filters[] = [
'field' => 'backupstart',
'operand' => 'BETWEEN',
'value' => $from,
'value2' => $to,
];
}
elseif ($from)
{
$filters[] = [
'field' => 'backupstart',
'operand' => '>=',
'value' => $from,
];
}
elseif ($to)
{
$toDate = clone Factory::getDate($to);
$to = $toDate->format('Y-m-d') . ' 23:59:59';
$filters[] = [
'field' => 'backupstart',
'operand' => '<=',
'value' => $to,
];
}
if ($model->getState('filter.origin'))
{
$filters[] = [
'field' => 'origin',
'operand' => '=',
'value' => $model->getState('filter.origin'),
];
}
if ($model->getState('filter.profile'))
{
$filters[] = [
'field' => 'profile_id',
'operand' => '=',
'value' => (int) $model->getState('filter.profile'),
];
}
if ($model->getState('filter.frozen') == 1)
{
$filters[] = [
'field' => 'frozen',
'operand' => '=',
'value' => 1,
];
}
elseif ($model->getState('filter.frozen') == 2)
{
$filters[] = [
'field' => 'frozen',
'operand' => '=',
'value' => 0,
];
}
if (empty($filters))
{
$filters = null;
}
return $filters;
}
/**
* Get the list ordering in a format that Akeeba Engine understands
*
* @return array
*/
private function getOrdering()
{
$model = $this->getModel('Statistics');
return [
'by' => $model->getState('list.ordering') ?? 'id',
'order' => $model->getState('list.direction') ?? 'DESC',
];
}
}