| Current Path : /home/w/u/e/wuectly/www/03cbe/ |
| Current File : /home/w/u/e/wuectly/www/03cbe/Controller.tar |
Controller.php 0000604 00000075041 15245531134 0007406 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller;
defined('_JEXEC') || die;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Exception\CannotGetName;
use FOF40\Controller\Exception\TaskNotFound;
use FOF40\Model\Model;
use FOF40\View\Exception\AccessForbidden;
use FOF40\View\View;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
use Joomla\CMS\Document\Document;
use Joomla\CMS\Factory as JoomlaFactory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
use stdClass;
/**
* Class Controller
*
* A generic MVC controller implementation
*
* @property-read \FOF40\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class Controller
{
/**
* Instance container.
*
* @var Controller
*/
protected static $instance;
/**
* The name of the controller
*
* @var array
*/
protected $name;
/**
* The mapped task that was performed.
*
* @var string
*/
protected $doTask;
/**
* Bit mask to enable routing through Route on redirects. The value can be:
*
* 0 = never
* 1 = frontend only
* 2 = backend only
* 3 = always
*
* @var int
*/
protected $autoRouting = 0;
/**
* Should I protect against state bleedover? When this is enabled the default model's state hash will be
* automatically set to include the controller name i.e. `com_example.controllerName.modelName.` instead of
* `com_example.modelName.`. This will happen ONLY if the preventStateBleedover flag is set, the controller and
* model names are different and the model doesn't set its own hash (or override getHash altogether).
*
* You should only need to enable this feature when you have multiple controllers using the _same_ Model as their
* default. For example, if you have a blog component with Latest and Posts Controllers, both using the Posts Model
* as their default Model the state variables set in the latest posts page would bleed over to the posts page. This
* can include filtering and pagination preferences, resulting in a confusing experience for the user.
*
* Caveat: if you are using a different Controller class for singular / plural view names you will need to override
* getModel() yourself. Otherwise the state of the singular view would be disjointed from the state of the
* plural view (since the Controller names are different). That's the reason why this feature is turned off
* by default.
*
* False = same behavior as FOF 3.0.0 to 3.1.1 inclusive.
*
* @var bool
*/
protected $preventStateBleedover = false;
/**
* Redirect message.
*
* @var string
*/
protected $message;
/**
* Redirect message type.
*
* @var string
*/
protected $messageType;
/**
* Array of class methods
*
* @var array
*/
protected $methods;
/**
* The set of search directories for resources (views).
*
* @var array
*/
protected $paths;
/**
* URL for redirection.
*
* @var string|null
*/
protected $redirect;
/**
* Current or most recently performed task.
*
* @var string
*/
protected $task;
/**
* Array of class methods to call for a given task.
*
* @var array
*/
protected $taskMap;
/**
* The current view name; you can override it in the configuration
*
* @var string
*/
protected $view = '';
/**
* The current layout; you can override it in the configuration
*
* @var string
*/
protected $layout;
/**
* A cached copy of the class configuration parameter passed during initialisation
*
* @var array
*/
protected $config = [];
/**
* Overrides the name of the view's default model
*
* @var string
*/
protected $modelName;
/**
* Overrides the name of the view's default view
*
* @var string
*/
protected $viewName;
/**
* An array of Model instances known to this Controller
*
* @var array[Model]
*/
protected $modelInstances = [];
/**
* An array of View instances known to this Controller
*
* @var array[View]
*/
protected $viewInstances = [];
/**
* The container attached to this Controller
*
* @var Container
*/
protected $container;
/**
* The tasks for which caching should be enabled by default
*
* @var array
*/
protected $cacheableTasks = [];
/**
* How user group membership affects caching. The values are:
* - 0 : Not taken into account, everyone sees the same page, always
* - 1 : Only user groups are taken into account (default behaviour of FOF 3.0 to 3.4.2)
* - 2 : The user ID itself is taken into account
*
* @var bool
* @since 3.4.3
*/
protected $userCaching = 1;
/**
* An associative array for required ACL privileges per task. For example:
* array(
* 'edit' => 'core.edit',
* 'jump' => 'foobar.jump',
* 'alwaysallow' => 'true',
* 'neverallow' => 'false'
* );
*
* You can use the notation '@task' which means 'apply the same privileges as "task"'. If you create a reference
* back to yourself (e.g. 'mytask' => array('@mytask')) it will return TRUE.
*
* @var array
*/
protected $taskPrivileges = [];
/**
* Enable CSRF protection on selected tasks. The possible values are:
*
* 0 Disabled; no token checks are performed
* 1 Enabled; token checks are always performed
* 2 Only on HTML requests and backend; token checks are always performed in the back-end and in the front-end
* only when format is 'html'
* 3 Only on back-end; token checks are performed only in the back-end
*
* @var integer
*/
protected $csrfProtection = 2;
/**
* Public constructor of the Controller class. You can pass the following variables in the $config array:
* name string The name of the Controller. Default: auto detect from the class name
* default_task string The task to use when none is specified. Default: main
* autoRouting int See the autoRouting property
* csrfProtection int See the csrfProtection property
* viewName string The view name. Default: the same as the controller name
* modelName string The model name. Default: the same as the controller name
* viewConfig array The configuration overrides for the View.
* modelConfig array The configuration overrides for the Model.
*
* @param Container $container The application container
* @param array $config The configuration array
*
* @return Controller
*/
public function __construct(Container $container, array $config = [])
{
// Initialise
$this->methods = [];
$this->message = null;
$this->messageType = 'message';
$this->paths = [];
$this->redirect = null;
$this->taskMap = [];
// Get a local copy of the container
$this->container = $container;
// Determine the methods to exclude from the base class.
$xMethods = get_class_methods('\\FOF40\\Controller\\Controller');
// Get the public methods in this class using reflection.
$r = new \ReflectionClass($this);
$rMethods = $r->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($rMethods as $rMethod)
{
$mName = $rMethod->getName();
// If the developer screwed up and declared one of the helper method public do NOT make them available as
// tasks.
if ((substr($mName, 0, 8) == 'onBefore') || (substr($mName, 0, 7) == 'onAfter') || substr($mName, 0, 1) == '_')
{
continue;
}
// Add default display method if not explicitly declared.
if (!in_array($mName, $xMethods) || $mName == 'display' || $mName == 'main')
{
$this->methods[] = $mName;
// Auto register the methods as tasks.
$this->taskMap[$mName] = $mName;
}
}
if (isset($config['name']))
{
$this->name = $config['name'];
}
// Get the default values for the component and view names
$this->view = $this->getName();
$this->layout = $this->input->getCmd('layout', null);
// If the default task is set, register it as such
if (array_key_exists('default_task', $config) && !empty($config['default_task']))
{
$this->registerDefaultTask($config['default_task']);
}
else
{
$this->registerDefaultTask('main');
}
// Cache the config
$this->config = $config;
// Set any model/view name overrides
if (array_key_exists('viewName', $config) && !empty($config['viewName']))
{
$this->setViewName($config['viewName']);
}
if (array_key_exists('modelName', $config) && !empty($config['modelName']))
{
$this->setModelName($config['modelName']);
}
// Apply the autoRouting preference
if (array_key_exists('autoRouting', $config))
{
$this->autoRouting = (int) $config['autoRouting'];
}
// Apply the csrfProtection preference
if (array_key_exists('csrfProtection', $config))
{
$this->csrfProtection = (int) $config['csrfProtection'];
}
// Apply the preventStateBleedover preference
if (array_key_exists('preventStateBleedover', $config))
{
$this->preventStateBleedover = (bool) ((int) $config['preventStateBleedover']);
}
}
/**
* Magic get method. Handles magic properties:
* $this->input mapped to $this->container->input
*
* @param string $name The property to fetch
*
* @return mixed|null
*/
public function __get(string $name)
{
// Handle $this->input
if ($name == 'input')
{
return $this->container->input;
}
// Property not found; raise error
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/**
* Executes a given controller task. The onBefore<task> and onAfter<task>
* methods are called automatically if they exist.
*
* @param string $task The task to execute, e.g. "browse"
*
* @return mixed|bool False on execution failure
*
* @throws TaskNotFound When the task is not found
*/
public function execute(string $task)
{
$this->task = $task;
if (!isset($this->taskMap[$task]) && !isset($this->taskMap['__default']))
{
throw new TaskNotFound(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
}
$result = $this->triggerEvent('onBeforeExecute', [&$task]);
if ($result === false)
{
return false;
}
$eventName = 'onBefore' . ucfirst($task);
$result = $this->triggerEvent($eventName);
if ($result === false)
{
return false;
}
// Do not allow the display task to be directly called
if (isset($this->taskMap[$task]))
{
$doTask = $this->taskMap[$task];
}
elseif (isset($this->taskMap['__default']))
{
$doTask = $this->taskMap['__default'];
}
else
{
$doTask = null;
}
// Record the actual task being fired
$this->doTask = $doTask;
$ret = $this->$doTask();
$eventName = 'onAfter' . ucfirst($task);
$result = $this->triggerEvent($eventName);
if ($result === false)
{
return false;
}
$result = $this->triggerEvent('onAfterExecute', [$task]);
if ($result === false)
{
return false;
}
return $ret;
}
/**
* Default task. Assigns a model to the view and asks the view to render
* itself.
*
* YOU MUST NOT USE THIS TASK DIRECTLY IN A URL. It is supposed to be
* used ONLY inside your code. In the URL, use task=browse instead.
*
* @param bool $cachable Is this view cacheable?
* @param array|null $urlparams Add your safe URL parameters (see further down in the code)
* @param string $tpl The name of the template file to parse
*
* @return void
*
* @throws Exception
*/
public function display(bool $cachable = false, ?array $urlparams = null, ?string $tpl = null): void
{
$document = $this->container->platform->getDocument();
if ($document instanceof Document)
{
$viewType = $document->getType();
}
else
{
$viewType = $this->input->getCmd('format', 'html');
}
$view = $this->getView();
$view->setTask($this->task);
$view->setDoTask($this->doTask);
// Get/Create the model
if ($model = $this->getModel())
{
// Push the model into the view (as default)
$view->setDefaultModel($model);
}
// Set the layout
if (!is_null($this->layout))
{
$view->setLayout($this->layout);
}
$conf = $this->container->platform->getConfig();
if ($cachable && ($viewType != 'feed') && ($conf->get('caching') >= 1))
{
// Get a Cache object
$option = $this->input->get('option', 'com_foobar', 'cmd');
// Set up a cache ID based on component, view, task and user group assignment
$user = $this->container->platform->getUser();
$groups = $user->guest ? [] : $user->groups;
$userId = $user->guest ? 0 : $user->id;
switch ($this->userCaching)
{
case 0:
// Developer chose to apply the same caching to everyone
$groups = [];
$userId = 0;
break;
case 1:
// Developer chose to apply caching per user group membership only
$userId = 0;
break;
}
$importantParameters = [];
// Set up safe URL parameters
if (!is_array($urlparams))
{
$urlparams = [
'option' => 'CMD',
'view' => 'CMD',
'task' => 'CMD',
'format' => 'CMD',
'layout' => 'CMD',
'id' => 'INT',
];
}
if (is_array($urlparams))
{
/** @var CMSApplication $app */
$app = JoomlaFactory::getApplication();
$registeredurlparams = null;
$registeredurlparams = !empty($app->registeredurlparams) ? $app->registeredurlparams : new stdClass;
foreach ($urlparams as $key => $value)
{
// Add your safe url parameters with variable type as value {@see InputFilter::clean()}.
$registeredurlparams->$key = $value;
// Add the URL-important parameters into the array
$importantParameters[$key] = $this->input->get($key, null, $value);
}
$app->registeredurlparams = $registeredurlparams;
}
// Create the cache ID after setting the registered URL params, as they are used to generate the ID
$cacheId = md5(serialize([
Cache::makeId(), $view->getName(), $this->doTask, $groups, $userId, $importantParameters,
]));
// Get the cached view or cache the current view
try
{
/** @var \Joomla\CMS\Cache\Controller\ViewController $cache */
$cache = JoomlaFactory::getCache($option, 'view');
$cache->get($view, 'display', $cacheId);
}
catch (CacheExceptionInterface $e)
{
// Display without caching
$view->display($tpl);
}
}
else
{
// Display without caching
$view->display($tpl);
}
}
/**
* Alias to the display() task
*
* @codeCoverageIgnore
*
* @throws Exception
*/
public function main()
{
$this->display();
}
/**
* Returns a named Model object
*
* @param string $name The Model name. If null we'll use the modelName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the Model. If skipped
* we will use $this->config
*
* @return Model The instance of the Model known to this Controller
*/
public function getModel(?string $name = null, array $config = [])
{
if (!empty($name))
{
$modelName = $name;
}
elseif (!empty($this->modelName))
{
$modelName = $this->modelName;
}
else
{
$modelName = $this->view;
}
// Do we already have an instance of that model cached?
if (array_key_exists($modelName, $this->modelInstances))
{
return $this->modelInstances[$modelName];
}
if (empty($config) && isset($this->config['modelConfig']))
{
$config = $this->config['modelConfig'];
}
if (empty($name))
{
$config['modelTemporaryInstance'] = true;
$controllerName = $this->getName();
if ($controllerName !== $modelName)
{
$config['hash_view'] = $controllerName;
}
}
else
{
// Other classes are loaded with persistent state disabled and their state/input blanked out
$config['modelTemporaryInstance'] = false;
$config['modelClearState'] = true;
$config['modelClearInput'] = true;
}
$this->modelInstances[$modelName] = $this->container->factory->model(ucfirst($modelName), $config);
return $this->modelInstances[$modelName];
}
/**
* Returns a named View object
*
* @param string $name The Model name. If null we'll use the modelName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the Model. If skipped
* we will use $this->config
*
* @return View The instance of the Model known to this Controller
*/
public function getView(?string $name = null, array $config = [])
{
if (!empty($name))
{
$viewName = $name;
}
elseif (!empty($this->viewName))
{
$viewName = $this->viewName;
}
else
{
$viewName = $this->view;
}
if (!array_key_exists($viewName, $this->viewInstances))
{
if (empty($config) && isset($this->config['viewConfig']))
{
$config = $this->config['viewConfig'];
}
$viewType = $this->input->getCmd('format', 'html');
// Get the model's class name
$this->viewInstances[$viewName] = $this->container->factory->view($viewName, $viewType, $config);
}
return $this->viewInstances[$viewName];
}
/**
* Pushes a named view to the Controller
*
* @param string $viewName The name of the View
* @param View $view The actual View object to push
*
* @return void
*/
public function setView(string $viewName, View &$view): void
{
$this->viewInstances[$viewName] = $view;
}
/**
* Set the name of the view to be used by this Controller
*
* @param string $viewName The name of the view
*
* @return void
*/
public function setViewName(string $viewName): void
{
$this->viewName = $viewName;
}
/**
* Set the name of the model to be used by this Controller
*
* @param string $modelName The name of the model
*
* @return void
*/
public function setModelName(string $modelName): void
{
$this->modelName = $modelName;
}
/**
* Pushes a named model to the Controller
*
* @param string $modelName The name of the Model
* @param Model $model The actual Model object to push
*
* @return void
*/
public function setModel(string $modelName, Model &$model): void
{
$this->modelInstances[$modelName] = $model;
}
/**
* Method to get the controller name
*
* The controller name is set by default parsed using the classname, or it can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the controller
*
* @throws CannotGetName If it's impossible to determine the name and it's not set
*/
public function getName(): string
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/(.*)\\\\Controller\\\\(.*)/i', get_class($this), $r))
{
throw new CannotGetName(Text::_('LIB_FOF40_CONTROLLER_ERR_GET_NAME'), 500);
}
$this->name = $r[2];
}
return $this->name;
}
/**
* Get the last task that is being performed or was most recently performed.
*
* @return string The task that is being performed or was most recently performed.
*/
public function getTask(): string
{
return $this->task;
}
/**
* Gets the available tasks in the controller.
*
* @return string[] Array of task names.
*/
public function getTasks(): array
{
return $this->methods;
}
/**
* Redirects the browser or returns false if no redirect is set.
*
* @return boolean False if no redirect exists.
*/
public function redirect(): bool
{
if (!empty($this->redirect))
{
$this->container->platform->redirect($this->redirect, 301, $this->message, $this->messageType);
return true;
}
return false;
}
/**
* Register the default task to perform if a mapping is not found.
*
* @param string $method The name of the method in the derived class to perform if a named task is not found.
*
* @return Controller This object to support chaining.
*/
public function registerDefaultTask(string $method): Controller
{
$this->registerTask('__default', $method);
return $this;
}
/**
* Register (map) a task to a method in the class.
*
* @param string $task The task.
* @param string $method The name of the method in the derived class to perform for this task.
*
* @return Controller This object to support chaining.
*/
public function registerTask(string $task, string $method): Controller
{
if (in_array($method, $this->methods))
{
$this->taskMap[$task] = $method;
}
return $this;
}
/**
* Unregister (unmap) a task in the class.
*
* @param string $task The task.
*
* @return Controller This object to support chaining.
*/
public function unregisterTask(string $task): Controller
{
unset($this->taskMap[$task]);
return $this;
}
/**
* Sets the internal message that is passed with a redirect
*
* @param string $text Message to display on redirect.
* @param string $type Message type. Optional, defaults to 'message'.
*
* @return string|null Previous message
*/
public function setMessage(string $text, string $type = 'message'): ?string
{
$previous = $this->message;
$this->message = $text;
$this->messageType = $type;
return $previous;
}
/**
* Set a URL for browser redirection.
*
* @param string $url URL to redirect to.
* @param string $msg Message to display on redirect. Optional, defaults to value set internally by
* controller, if any.
* @param string $type Message type. Optional, defaults to 'message' or the type set by a previous call to
* setMessage.
*
* @return Controller This object to support chaining.
*/
public function setRedirect(string $url, ?string $msg = null, ?string $type = null): Controller
{
// If we're parsing a non-SEF URL decide whether to use Route or not
if (strpos($url, 'index.php') === 0)
{
$isAdmin = $this->container->platform->isBackend();
$auto = false;
if (($this->autoRouting == 2 || $this->autoRouting == 3) && $isAdmin)
{
$auto = true;
}
if (($this->autoRouting == 1 || $this->autoRouting == 3) && !$isAdmin)
{
$auto = true;
}
if ($auto)
{
$url = Route::_($url, false);
}
/**
* Joomla 4 does not add the base URI to redirections.
*
* This means that all bare redirects, e.g. to 'index.php?option=com_example', no longer work correctly.
*
* In the frontend, if your site is located in a subdirectory e.g. /foobar you get redirected to
* /index.php?option=com_example instead of /foobar/index.php?option=com_example
*
* In the backend, you're redirected to /index.php?option=com_example instead of the expected
* /administrator/index.php?option=com_example which breaks your application since the backend redirects to
* the frontend.
*
* This is an undocumented b/c break in Joomla 4. It even breaks some of the core components...
*
* The following code detects bare redirect URLs and adds the base URI path if auto-routing has been
* disabled, automatically fixing the observed issue. It only does that on Joomla 4 since adding the base
* URI on Joomla 3 can cause redirection problems.
*/
if (!$auto && version_compare(JVERSION, '3.999.999', 'gt'))
{
$url = Uri::base() . $url;
}
}
// Set the redirection
$this->redirect = $url;
if ($msg !== null)
{
// Controller may have set this directly
$this->message = $msg;
}
// Ensure the type is not overwritten by a previous call to setMessage.
if (empty($this->messageType))
{
$this->messageType = 'message';
}
// If the type is explicitly set, set it.
if (!empty($type))
{
$this->messageType = $type;
}
return $this;
}
/**
* Returns true if there is a redirect set in the controller
*
* @return boolean
*/
public function hasRedirect(): bool
{
return !empty($this->redirect);
}
/**
* Provides CSRF protection through the forced use of a secure token. If the token doesn't match the one in the
* session we return false.
*
* @return bool
*
* @throws Exception
*/
protected function csrfProtection(): bool
{
static $isCli = null, $isAdmin = null;
$platform = $this->container->platform;
if (is_null($isCli))
{
$isCli = $platform->isCli();
$isAdmin = $platform->isBackend();
}
switch ($this->csrfProtection)
{
// Never
case 0:
return true;
// Always
case 1:
break;
// Only back-end and HTML format
case 2:
if ($isCli)
{
return true;
}
elseif (!$isAdmin && ($this->input->get('format', 'html', 'cmd') != 'html'))
{
return true;
}
break;
// Only back-end
case 3:
if (!$isAdmin)
{
return true;
}
break;
}
// Check for a session token
$token = $this->container->platform->getToken(false);
$hasToken = $this->input->get($token, false, 'none') == 1;
if (!$hasToken)
{
$hasToken = $this->input->get('_token', null, 'none') == $token;
}
if ($hasToken)
{
$view = $this->input->getCmd('view');
$task = $this->input->getCmd('task');
Log::add(
"FOF: You are using a legacy session token in (view, task)=($view, $task). Support for legacy tokens will go away. Use form tokens instead.",
Log::WARNING,
'deprecated'
);
}
// Check for a form token
if (!$hasToken)
{
$token = $this->container->platform->getToken(true);
$hasToken = $this->input->get($token, false, 'none') == 1;
if (!$hasToken)
{
$view = $this->input->getCmd('view');
$task = $this->input->getCmd('task');
Log::add(
"FOF: You are using the insecure _token form variable in (view, task)=($view, $task). Support for it will go away. Submit a variable with the token as the name and a value of 1 instead.",
Log::WARNING,
'deprecated'
);
$hasToken = $this->input->get('_token', null, 'none') == $token;
}
}
if (!$hasToken)
{
$platform->showErrorPage(new AccessForbidden(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')));
return false;
}
return true;
}
/**
* Triggers an object-specific event. The event runs both locally –if a suitable method exists– and through the
* Joomla! plugin system. A true/false return value is expected. The first false return cancels the event.
*
* EXAMPLE
* Component: com_foobar, Object name: item, Event: onBeforeSomething, Arguments: array(123, 456)
* The event calls:
* 1. $this->onBeforeSomething(123, 456)
* 2. $this->checkACL('@something') if there is no onBeforeSomething and the event starts with onBefore
* 3. Joomla! plugin event onComFoobarControllerItemBeforeSomething($this, 123, 456)
*
* @param string $event The name of the event, typically named onPredicateVerb e.g. onBeforeKick
* @param array $arguments The arguments to pass to the event handlers
*
* @return bool
*/
protected function triggerEvent(string $event, array $arguments = []): bool
{
$result = true;
// If there is an object method for this event, call it
if (method_exists($this, $event))
{
$result = $this->{$event}(...$arguments);
}
// If there is no handler method perform a simple ACL check
elseif (substr($event, 0, 8) == 'onBefore')
{
$task = substr($event, 8);
if (array_key_exists($task, $this->taskMap))
{
$result = $this->checkACL('@' . $task);
}
}
if ($result === false)
{
return false;
}
// All other event handlers live outside this object, therefore they need to be passed a reference to this
// objects as the first argument.
array_unshift($arguments, $this);
// If we have an "on" prefix for the event (e.g. onFooBar) remove it and stash it for later.
$prefix = '';
if (substr($event, 0, 2) == 'on')
{
$prefix = 'on';
$event = substr($event, 2);
}
// Get the component/model prefix for the event
$prefix .= 'Com' . ucfirst($this->container->bareComponentName) . 'Controller';
$prefix .= ucfirst($this->getName());
// The event name will be something like onComFoobarItemsBeforeSomething
$event = $prefix . $event;
// Call the Joomla! plugins
$results = $this->container->platform->runPlugins($event, $arguments);
return !in_array(false, $results, true);
}
/**
* Checks if the current user has enough privileges for the requested ACL area.
*
* @param string $area The ACL area, e.g. core.manage.
*
* @return boolean True if the user has the ACL privilege specified
*/
protected function checkACL(string $area): bool
{
$area = $this->getACLRuleFor($area);
if (is_bool($area))
{
return $area;
}
if (in_array(strtolower($area), ['false', '0', 'no', '403']))
{
return false;
}
if (in_array(strtolower($area), ['true', '1', 'yes']))
{
return true;
}
if (strtolower($area) == 'guest')
{
return $this->container->platform->getUser()->guest;
}
if (strtolower($area) == 'user')
{
return !$this->container->platform->getUser()->guest;
}
if (empty($area))
{
return true;
}
return $this->container->platform->authorise($area, $this->container->componentName);
}
/**
* Resolves @task and &callback notations for ACL privileges
*
* @param string $area The task notation to resolve
* @param array $oldAreas Areas we've already been redirected from, used to detect circular references
*
* @return string|bool The resolved ACL privilege
*/
protected function getACLRuleFor(string $area, array $oldAreas = [])
{
// If it's a ¬ation return the callback result
if (substr($area, 0, 1) == '&')
{
$oldAreas[] = $area;
$method = substr($area, 1);
// Method not found? Assume true.
if (!method_exists($this, $method))
{
return true;
}
$area = $this->$method();
return $this->getACLRuleFor($area, $oldAreas);
}
// If it's not an @notation return the raw string
if (substr($area, 0, 1) != '@')
{
return $area;
}
// Get the array index (other task)
$index = substr($area, 1);
// If the referenced task has no ACL map, return true
if (!isset($this->taskPrivileges[$index]))
{
$index = strtolower($index);
if (!isset($this->taskPrivileges[$index]))
{
return true;
}
}
// Get the new ACL area
$newArea = $this->taskPrivileges[$index];
$oldAreas[] = $area;
// Circular reference found
if (in_array($newArea, $oldAreas))
{
return true;
}
// We've found an ACL privilege. Return it.
if (substr($area, 0, 1) != '@')
{
return $newArea;
}
// We have another reference. Resolve it.
return $this->getACLRuleFor($newArea, $oldAreas);
}
}
Mixin/PredefinedTaskList.php 0000604 00000003215 15245531134 0012065 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller\Mixin;
// Protect from unauthorized access
defined('_JEXEC') || die();
/**
* Force a Controller to allow access to specific tasks only, no matter which tasks are already defined in this
* Controller.
*/
trait PredefinedTaskList
{
/**
* A list of predefined tasks. Trying to access any other task will result in the first task of this list being
* executed instead.
*
* @var array
*/
protected $predefinedTaskList = array();
/**
* Overrides the execute method to implement the predefined task list feature
*
* @param string $task The task to execute
*
* @return mixed The controller task result
*/
public function execute($task)
{
if (!in_array($task, $this->predefinedTaskList))
{
$task = reset($this->predefinedTaskList);
}
return parent::execute($task);
}
/**
* Sets the predefined task list and registers the first task in the list as the Controller's default task
*
* @param array $taskList The task list to register
*/
public function setPredefinedTaskList(array $taskList)
{
// First, unregister all known tasks which are not in the taskList
$allTasks = $this->getTasks();
foreach ($allTasks as $task)
{
if (in_array($task, $taskList))
{
continue;
}
$this->unregisterTask($task);
}
// Set the predefined task list
$this->predefinedTaskList = $taskList;
// Set the default task
$this->registerDefaultTask(reset($this->predefinedTaskList));
}
}
DataController.php 0000604 00000117067 15245531134 0010205 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller;
defined('_JEXEC') || die;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Exception\ItemNotFound;
use FOF40\Controller\Exception\LockedRecord;
use FOF40\Controller\Exception\NotADataModel;
use FOF40\Controller\Exception\NotADataView;
use FOF40\Controller\Exception\TaskNotFound;
use FOF40\Model\DataModel;
use FOF40\View\DataView\DataViewInterface;
use FOF40\View\View;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Response\JsonResponse;
/**
* Database-aware Controller
*
* @property-read \FOF40\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class DataController extends Controller
{
/**
* Variables that should be taken in account while working with the cache. You can set them in Controller
* constructor or inside onBefore* methods
*
* @var null|array
*/
protected $cacheParams = null;
/**
* An associative array for required ACL privileges per task. For example:
* array(
* 'edit' => 'core.edit',
* 'jump' => 'foobar.jump',
* 'alwaysallow' => 'true',
* 'neverallow' => 'false'
* );
*
* You can use the notation '@task' which means 'apply the same privileges as "task"'. If you create a reference
* back to yourself (e.g. 'mytask' => array('@mytask')) it will return TRUE.
*
* @var array
*/
protected $taskPrivileges = [
// Special privileges
'*editown' => 'core.edit.own', // Privilege required to edit own record
// Standard tasks
'add' => 'core.create',
'apply' => '&getACLForApplySave', // Apply task: call the getACLForApplySave method
'archive' => 'core.edit.state',
'cancel' => 'core.edit.state',
'copy' => '@add', // Maps copy ACLs to the add task
'edit' => 'core.edit',
'loadhistory' => '@edit', // Maps loadhistory ACLs to the edit task
'orderup' => 'core.edit.state',
'orderdown' => 'core.edit.state',
'publish' => 'core.edit.state',
'remove' => 'core.delete',
'forceRemove' => 'core.delete',
'save' => '&getACLForApplySave', // Save task: call the getACLForApplySave method
'savenew' => 'core.create',
'saveorder' => 'core.edit.state',
'trash' => 'core.edit.state',
'unpublish' => 'core.edit.state',
];
/**
* An indexed array of default values for the add task. Since the add task resets the model you can't set these
* values directly to the model. Instead, the defaultsForAdd values will be fed to model's bind() after it's reset
* and before the session-stored item data is bound to the model object.
*
* @var array
*/
protected $defaultsForAdd = [];
/**
* Public constructor of the Controller class. You can pass the following variables in the $config array,
* on top of what you already have in the base Controller class:
*
* taskPrivileges array ACL privileges for each task
* cacheableTasks array The cache-enabled tasks
*
* @param Container $container The application container
* @param array $config The configuration array
*/
public function __construct(Container $container, array $config = [])
{
parent::__construct($container, $config);
// Set up a default model name if none is provided
if (empty($this->modelName))
{
$this->modelName = $container->inflector->pluralize($this->view);
}
// Set up a default view name if none is provided
if (empty($this->viewName))
{
$this->viewName = $container->inflector->pluralize($this->view);
}
if (isset($config['cacheableTasks']))
{
if (!is_array($config['cacheableTasks']))
{
$config['cacheableTasks'] = explode(',', $config['cacheableTasks']);
$config['cacheableTasks'] = array_map('trim', $config['cacheableTasks']);
}
$this->cacheableTasks = $config['cacheableTasks'];
}
elseif ($this->container->platform->isBackend())
{
$this->cacheableTasks = [];
}
else
{
$this->cacheableTasks = ['browse', 'read'];
}
if (!isset($config['taskPrivileges']))
{
return;
}
if (!is_array($config['taskPrivileges']))
{
return;
}
$this->taskPrivileges = array_merge($this->taskPrivileges, $config['taskPrivileges']);
}
/**
* Executes a given controller task. The onBefore<task> and onAfter<task> methods are called automatically if they
* exist.
*
* If $task == 'default' we will determine the CRUD task to use based on the view name and HTTP verb in the request,
* overriding the routing.
*
* @param string $task The task to execute, e.g. "browse"
*
* @return null|bool False on execution failure
*
* @throws TaskNotFound When the task is not found
*/
public function execute(string $task)
{
if ($task == 'default')
{
$task = $this->getCrudTask();
}
return parent::execute($task);
}
/**
* Returns a named View object
*
* @param string $name The View name. If null we'll use the viewName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the View. If skipped
* we will use $this->config
*
* @return View|DataViewInterface The instance of the View known to this Controller
*
* @throws NotADataView If the View found does not implement the DataViewInterface
*/
public function getView(?string $name = null, array $config = [])
{
if (!empty($name))
{
$viewName = $name;
}
elseif (!empty($this->viewName))
{
$viewName = $this->viewName;
}
else
{
$viewName = $this->view;
}
if (!array_key_exists($viewName, $this->viewInstances))
{
if (empty($config) && isset($this->config['viewConfig']))
{
$config = $this->config['viewConfig'];
}
$viewType = $this->input->getCmd('format', 'html');
// Get the model's class name
$this->viewInstances[$viewName] = $this->container->factory->view($viewName, $viewType, $config);
}
if (($this->viewInstances[$viewName] instanceof View) && !($this->viewInstances[$viewName] instanceof DataViewInterface))
{
throw new NotADataView();
}
return $this->viewInstances[$viewName];
}
/**
* Implements a default browse task, i.e. read a bunch of records and send
* them to the browser.
*
* @return void
* @throws Exception
*/
public function browse(): void
{
// Initialise the savestate
$saveState = $this->input->get('savestate', -999, 'int');
if ($saveState == -999)
{
$saveState = true;
}
$this->getModel()->savestate($saveState);
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('browse', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record read. The id set in the request is passed to the model and
* then the item layout is used to render the result.
*
* @return void
*
* @throws ItemNotFound When the item is not found
* @throws Exception
*/
public function read(): void
{
// Load the model
/** @var DataModel $model */
$model = $this->getModel()->savestate(false);
// If there is no record loaded, try loading a record based on the id passed in the input object
if (!$model->getId())
{
$ids = $this->getIDsFromRequest($model);
if ($model->getId() != reset($ids))
{
$key = strtoupper($this->container->componentName . '_ERR_' . $model->getName() . '_NOTFOUND');
throw new ItemNotFound(Text::_($key), 404);
}
}
// Set the layout to item, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'item';
}
elseif ($this->layout == 'default')
{
$this->layout = 'item';
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('read', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record add. The form layout is used to present a blank page.
*
* @return void
* @throws Exception
*/
public function add(): void
{
// Load and reset the model
$model = $this->getModel()->savestate(false);
$model->reset();
// Set the layout to form, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'form';
}
elseif ($this->layout == 'default')
{
$this->layout = 'form';
}
if (!empty($this->defaultsForAdd))
{
$model->bind($this->defaultsForAdd);
}
// Get temporary data from the session, set if the save failed and we're redirected back here
$sessionKey = $this->viewName . '.savedata';
$itemData = $this->container->platform->getSessionVar($sessionKey, null, $this->container->componentName);
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
if (!empty($itemData))
{
$model->bind($itemData);
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('add', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record edit. The ID set in the request is passed to the model,
* then the form layout is used to edit the result.
*
* @return void
* @throws Exception
*/
public function edit(): void
{
// Load the model
/** @var DataModel $model */
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$userId = $this->container->platform->getUser()->id;
try
{
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->lock();
}
catch (Exception $e)
{
// Redirect on error
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
return;
}
// Set the layout to form, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'form';
}
elseif ($this->layout == 'default')
{
$this->layout = 'form';
}
// Get temporary data from the session, set if the save failed and we're redirected back here
$sessionKey = $this->viewName . '.savedata';
$itemData = $this->container->platform->getSessionVar($sessionKey, null, $this->container->componentName);
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
if (!empty($itemData))
{
$model->bind($itemData);
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('edit', $this->cacheableTasks), $this->cacheParams);
}
/**
* Save the incoming data and then return to the Edit task
*
* @return void
* @throws Exception
*/
public function apply(): void
{
// CSRF prevention
$this->csrfProtection();
// Redirect to the edit task
if (!$this->applySave())
{
return;
}
$id = $this->input->get('id', 0, 'int');
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
}
/**
* Duplicates selected items
*
* @return void
* @throws Exception
*/
public function copy(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model);
$error = null;
$copiedCount = 0;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->copy();
$copiedCount++;
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
return;
}
if ($copiedCount > 0)
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_COPIED');
$this->setRedirect($url, Text::_($textKey));
return;
}
$this->setRedirect($url);
}
/**
* Save the incoming data and then return to the Browse task
*
* @return void
* @throws Exception
*/
public function save(): void
{
// CSRF prevention
$this->csrfProtection();
if (!$this->applySave())
{
return;
}
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
}
/**
* Save the incoming data and then return to the Add task
*
* @return bool
* @throws Exception
*/
public function savenew(): bool
{
// CSRF prevention
$this->csrfProtection();
if (!$this->applySave())
{
return false;
}
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->singularize($this->view) . '&task=add' . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
return true;
}
/**
* Save the incoming data as a copy of the given model and then redirect to the copied object edit view
*
* @return bool
* @throws Exception
*/
public function save2copy(): bool
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model);
$data = $this->input->getData();
unset($data[$model->getIdFieldName()]);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model = $model->copy($data);
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : $url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $model->getId() . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_COPIED');
$this->setRedirect($url, Text::_($textKey));
}
}
/**
* Cancel the edit, check in the record and return to the Browse task
*
* @return void
*/
public function cancel(): void
{
$model = $this->getModel()->tmpInstance()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
if ($model->getId())
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
try
{
$model->checkIn($userId);
}
catch (LockedRecord $e)
{
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
}
}
$model->unlock();
}
// Remove any saved data
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url);
}
/**
* Publish (set enabled = 1) an item.
*
* @return void
* @throws Exception
*/
public function publish(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = false;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->publish();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Unpublish (set enabled = 0) an item.
*
* @return void
* @throws Exception
*/
public function unpublish(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->unpublish();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Archive (set enabled = 2) an item.
*
* @return void
* @throws Exception
*/
public function archive(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->archive();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Trash (set enabled = -2) an item.
*
* @return void
* @throws Exception
*/
public function trash(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->trash();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Check in (unlock) items
*
* @return void
* @throws Exception
*/
public function checkin(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->checkIn();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Saves the order of the items
*
* @return void
* @throws Exception
*/
public function saveorder(): void
{
// CSRF prevention
$this->csrfProtection();
$type = null;
$msg = null;
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$orders = $this->input->get('order', [], 'array');
// Before saving the order, I have to check I the table really supports the ordering feature
if (!$model->hasField('ordering'))
{
$msg = sprintf('%s does not support ordering.', $model->getTableName());
$type = 'error';
}
else
{
$ordering = $model->getFieldAlias('ordering');
// Several methods could throw exceptions, so let's wrap everything in a try-catch
try
{
if (($n = count($ids)) !== 0)
{
for ($i = 0; $i < $n; $i++)
{
$item = $model->find($ids[$i]);
$neworder = (int) $orders[$i];
if (!($item instanceof DataModel))
{
continue;
}
if ($item->getId() == $ids[$i])
{
$item->$ordering = $neworder;
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->save($item);
}
}
}
$model->reorder();
}
catch (Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $msg, $type);
}
/**
* Moves selected items one position down the ordering list
*
* @return void
* @throws Exception
*/
public function orderdown(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$error = null;
try
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->move(1);
$status = true;
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Moves selected items one position up the ordering list
*
* @return void
* @throws Exception
*/
public function orderup(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$error = null;
try
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->move(-1);
$status = true;
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Delete or trash selected item(s). The model's softDelete flag determines if the items should be trashed (enabled
* state changed to -2) or deleted (completely removed from database)
*
* @return void
*/
public function remove(): void
{
$this->deleteOrTrash();
}
/**
* Deletes the selected item(s). Unlike remove() this method will force delete the record (completely removed from
* database)
*
* @return void
*/
public function forceRemove(): void
{
$this->deleteOrTrash(true);
}
/**
* Returns a named Model object. Makes sure that the Model is a database-aware model, throwing an exception
* otherwise, when $name is null.
*
* @param string $name The Model name. If null we'll use the modelName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the Model. If skipped
* we will use $this->config
*
* @return DataModel The instance of the Model known to this Controller
*
* @throws NotADataModel When the model type doesn't match our expectations
*/
public function getModel(?string $name = null, array $config = [])
{
$model = parent::getModel($name, $config);
if (is_null($name) && !($model instanceof DataModel))
{
throw new NotADataModel('Model ' . get_class($model) . ' is not a database-aware Model');
}
return $model;
}
/**
* Gets the list of IDs from the request data
*
* @param DataModel $model The model where the record will be loaded
* @param bool $loadRecord When true, the record matching the *first* ID found will be loaded into $model
*
* @return int[]
*/
public function getIDsFromRequest(DataModel &$model, bool $loadRecord = true): array
{
// Get the ID or list of IDs from the request or the configuration
$cid = $this->input->get('cid', [], 'array');
$id = $this->input->getInt('id', 0);
$kid = $this->input->getInt($model->getIdFieldName(), 0);
$ids = [];
if (is_array($cid) && !empty($cid))
{
$ids = $cid;
}
elseif (empty($id))
{
if (!empty($kid))
{
$ids = [$kid];
}
}
else
{
$ids = [$id];
}
if ($loadRecord && !empty($ids))
{
$id = reset($ids);
$model->find(['id' => $id]);
}
return $ids;
}
/**
* Method to load a row from version history
*
* @return boolean True if the content history is reverted, false otherwise
*
* @since 2.2
*/
public function loadhistory(): bool
{
$model = $this->getModel();
$model->lock();
$historyId = $this->input->get('version_id', null, 'integer');
$alias = $this->container->componentName . '.' . $this->view;
$returnUrl = 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
if (!empty($customURL))
{
$returnUrl = $customURL;
}
try
{
$model->loadhistory($historyId, $alias);
}
catch (Exception $e)
{
$this->setRedirect($returnUrl, $e->getMessage(), 'error');
$model->unlock();
return false;
}
// Access check.
if (!$this->checkACL('@loadhistory'))
{
$this->setRedirect($returnUrl, Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
$model->unlock();
return false;
}
$model->store();
$this->setRedirect($returnUrl, Text::sprintf('JLIB_APPLICATION_SUCCESS_LOAD_HISTORY', $model->getState('save_date'), $model->getState('version_note')));
return true;
}
/**
* Gets a URL suffix with the Itemid parameter. If it's not the front-end of the site, or if
* there is no Itemid set it returns an empty string.
*
* @return string The &Itemid=123 URL suffix, or an empty string if Itemid is not applicable
*/
public function getItemidURLSuffix(): string
{
if ($this->container->platform->isFrontend() && ($this->input->getCmd('Itemid', 0) != 0))
{
return '&Itemid=' . $this->input->getInt('Itemid', 0);
}
else
{
return '';
}
}
/**
* Deal with JSON format: no redirects needed
*
* @param string $task The task being executed
*
* @return bool True if everything went well
*
* @throws Exception
*/
protected function onAfterExecute(string $task): bool
{
// JSON shouldn't have redirects
if ($this->hasRedirect() && $this->input->getCmd('format', 'html') == 'json')
{
// Error: deal with it in REST api way
if ($this->messageType == 'error')
{
$response = new JsonResponse($this->message, $this->message, true);
echo $response;
$this->redirect = false;
$this->container->platform->setHeader('Status', 500);
return true;
}
// Not an error, avoid redirect and display the record(s)
$this->redirect = false;
$this->display();
return true;
}
return true;
}
/**
* Determines the CRUD task to use based on the view name and HTTP verb used in the request.
*
* @return string The CRUD task (browse, read, edit, delete)
*/
protected function getCrudTask(): string
{
// By default, a plural view means 'browse' and a singular view means 'edit'
$view = $this->input->getCmd('view', null);
$task = $this->container->inflector->isPlural($view) ? 'browse' : 'edit';
// If the task is 'edit' but there's no logged in user switch to a 'read' task
if (($task == 'edit') && !$this->container->platform->getUser()->id)
{
$task = 'read';
}
// Check if there is an id passed in the request
$id = $this->input->get('id', null, 'int');
if ($id == 0)
{
$ids = $this->input->get('ids', [], 'array');
if (!empty($ids))
{
$id = array_shift($ids);
}
}
// Get the request HTTP verb
$requestMethod = 'GET';
if (isset($_SERVER['REQUEST_METHOD']))
{
$requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);
}
// Alter the task based on the verb
switch ($requestMethod)
{
// POST and PUT result in a record being saved; no ID means creating a new record
case 'POST':
case 'PUT':
$task = 'save';
break;
// DELETE results in a record being deleted, as long as there is an ID
case 'DELETE':
if ($id)
{
$task = 'remove';
}
break;
// GET results in browse, edit or add depending on the ID
case 'GET':
default:
// If it's an edit without an ID or ID=0, it's really an add
if (($task == 'edit') && ($id == 0))
{
$task = 'add';
}
break;
}
return $task;
}
/**
* Checks if the current user has enough privileges for the requested ACL area. This overridden method supports
* asset tracking as well.
*
* @param string $area The ACL area, e.g. core.manage
*
* @return boolean True if the user has the ACL privilege specified
*/
protected function checkACL(string $area): bool
{
$area = $this->getACLRuleFor($area);
$result = parent::checkACL($area);
// First, check if there is an asset for this record
/** @var DataModel $model */
$model = $this->getModel();
$ids = null;
if (is_object($model) && ($model instanceof DataModel) && $model->isAssetsTracked())
{
$ids = $this->getIDsFromRequest($model, false);
}
// No IDs tracked, return parent's result
if (empty($ids))
{
return $result;
}
// Asset tracking
if (!is_array($ids))
{
$ids = [$ids];
}
$resource = $this->container->inflector->singularize($this->view);
$isEditState = ($area == 'core.edit.state');
foreach ($ids as $id)
{
$asset = $this->container->componentName . '.' . $resource . '.' . $id;
// Dedicated permission found, check it!
$platform = $this->container->platform;
if ($platform->authorise($area, $asset))
{
return true;
}
// Fallback on edit.own, if not edit.state. First test if the permission is available.
$editOwn = $this->getACLRuleFor('@*editown');
if ((!$isEditState) && ($platform->authorise($editOwn, $asset)))
{
$model->load($id);
if (!$model->hasField('created_by'))
{
return false;
}
// Now test the owner is the user.
$owner_id = (int) $model->getFieldValue('created_by', null);
// If the owner matches 'me' then do the test.
return $owner_id === $platform->getUser()->id;
}
}
// No result found? Not authorised.
return false;
}
/**
* Automatically decides whether to trash or delete a record based in the $forceDelete parameter
*
* @param bool $forceDelete Should I delete the record? If false, the record will be trashed instead.
*
* @throws Exception
*/
protected function deleteOrTrash(bool $forceDelete = false): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
if ($forceDelete)
{
$model->forceDelete();
}
else
{
$model->delete();
}
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_DELETED');
$this->setRedirect($url, Text::_($textKey));
}
}
/**
* Common method to handle apply and save tasks
*
* @return bool True on success
*/
protected function applySave(): bool
{
// Load the model
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$userId = $this->container->platform->getUser()->id;
$id = $model->getId();
$data = $this->input->getData();
if ($model->isLocked($userId))
{
try
{
$model->checkIn($userId);
}
catch (LockedRecord $e)
{
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$eventName = 'onAfterApplySaveError';
$result = $this->triggerEvent($eventName, [&$data, $id, $e]);
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
return false;
}
}
// Set the layout to form, if it's not set in the URL
if (is_null($this->layout))
{
$this->layout = 'form';
}
// Save the data
$status = true;
$error = null;
try
{
$eventName = 'onBeforeApplySave';
$this->triggerEvent($eventName, [&$data]);
if ($id != 0)
{
// Try to check-in the record if it's not a new one
$model->unlock();
}
// Save the data
$model->save($data);
$eventName = 'onAfterApplySave';
$this->triggerEvent($eventName, [&$data, $model->getId()]);
$this->input->set('id', $model->getId());
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
$eventName = 'onAfterApplySaveError';
$result = $this->triggerEvent($eventName, [&$data, $model->getId(), $e]);
}
if (!$status)
{
// Cache the item data in the session. We may need to reuse them if the save fails.
$itemData = $model->getData();
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, $itemData, $this->container->componentName);
// Redirect on error
$id = $model->getId();
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
if (!empty($customURL))
{
$url = $customURL;
}
elseif ($id != 0)
{
$url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
}
else
{
$url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=add' . $this->getItemidURLSuffix();
}
$this->setRedirect($url, $error, 'error');
}
else
{
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
}
return $status;
}
/**
* Gets the applicable ACL privilege for the apply and save tasks. The value returned is:
* - @add if the record's ID is empty / record doesn't exist
* - True if the ACL privilege of the edit task (@edit) is allowed
* - @editown if the owner of the record (field user_id, userid or user) is the same as the logged in user
* - False if the record is not owned by the logged in user and the user doesn't have the @edit privilege
*
* @return bool|string
*/
protected function getACLForApplySave()
{
$model = $this->getModel();
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$id = $model->getId();
if (!$id)
{
return '@add';
}
if ($this->checkACL('@edit'))
{
return true;
}
$user = $this->container->platform->getUser();
$uid = 0;
if ($model->hasField('user_id'))
{
$uid = $model->getFieldValue('user_id');
}
elseif ($model->hasField('userid'))
{
$uid = $model->getFieldValue('userid');
}
elseif ($model->hasField('user'))
{
$uid = $model->getFieldValue('user');
}
if (!empty($uid) && !$user->guest && ($user->id == $uid))
{
return '@editown';
}
return false;
}
}
Exception/ItemNotFound.php 0000604 00000000605 15245531134 0011566 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use RuntimeException;
/**
* Exception thrown when we can't find the requested item in a read task
*/
class ItemNotFound extends RuntimeException
{
}
Exception/CannotGetName.php 0000604 00000000566 15245531134 0011704 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use RuntimeException;
/**
* Exception thrown when we can't get a Controller's name
*/
class CannotGetName extends RuntimeException
{
}
Exception/LockedRecord.php 0000604 00000001255 15245531134 0011555 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use Exception;
use Joomla\CMS\Language\Text;
use RuntimeException;
/**
* Exception thrown when the provided Model is locked for writing by another user
*/
class LockedRecord extends RuntimeException
{
public function __construct(string $message = "", int $code = 403, Exception $previous = null)
{
if (empty($message))
{
$message = Text::_('LIB_FOF40_CONTROLLER_ERR_LOCKED');
}
parent::__construct($message, $code, $previous);
}
}
Exception/NotADataModel.php 0000604 00000000613 15245531134 0011626 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use InvalidArgumentException;
/**
* Exception thrown when the provided Model is not a DataModel
*/
class NotADataModel extends InvalidArgumentException
{
}
Exception/TaskNotFound.php 0000604 00000000641 15245531134 0011572 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use InvalidArgumentException;
/**
* Exception thrown when we can't find a suitable method to handle the requested task
*/
class TaskNotFound extends InvalidArgumentException
{
}
OutputController.php 0000604 00000010675 15245535226 0010637 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\CacheController;
use Joomla\CMS\Log\Log;
/**
* Joomla Cache output type object
*
* @since 1.7.0
*/
class OutputController extends CacheController
{
/**
* Cache data ID
*
* @var string
* @since 1.7.0
*/
protected $_id;
/**
* Cache data group
*
* @var string
* @since 1.7.0
*/
protected $_group;
/**
* Object to test locked state
*
* @var \stdClass
* @since 1.7.0
* @deprecated 4.0
*/
protected $_locktest = null;
/**
* Start the cache
*
* @param string $id The cache data ID
* @param string $group The cache data group
*
* @return boolean
*
* @since 1.7.0
* @deprecated 4.0
*/
public function start($id, $group = null)
{
Log::add(
__METHOD__ . '() is deprecated.',
Log::WARNING,
'deprecated'
);
// If we have data in cache use that.
$data = $this->cache->get($id, $group);
$this->_locktest = new \stdClass;
$this->_locktest->locked = null;
$this->_locktest->locklooped = null;
if ($data === false)
{
$this->_locktest = $this->cache->lock($id, $group);
if ($this->_locktest->locked == true && $this->_locktest->locklooped == true)
{
$data = $this->cache->get($id, $group);
}
}
if ($data !== false)
{
$data = unserialize(trim($data));
echo $data;
if ($this->_locktest->locked == true)
{
$this->cache->unlock($id, $group);
}
return true;
}
// Nothing in cache... let's start the output buffer and start collecting data for next time.
if ($this->_locktest->locked == false)
{
$this->_locktest = $this->cache->lock($id, $group);
}
ob_start();
ob_implicit_flush(false);
// Set id and group placeholders
$this->_id = $id;
$this->_group = $group;
return false;
}
/**
* Stop the cache buffer and store the cached data
*
* @return boolean True if the cache data was stored
*
* @since 1.7.0
* @deprecated 4.0
*/
public function end()
{
Log::add(
__METHOD__ . '() is deprecated.',
Log::WARNING,
'deprecated'
);
// Get data from output buffer and echo it
$data = ob_get_clean();
echo $data;
// Get the ID and group and reset the placeholders
$id = $this->_id;
$group = $this->_group;
$this->_id = null;
$this->_group = null;
// Get the storage handler and store the cached data
$ret = $this->cache->store(serialize($data), $id, $group);
if ($this->_locktest->locked == true)
{
$this->cache->unlock($id, $group);
}
return $ret;
}
/**
* Get stored cached data by ID and group
*
* @param string $id The cache data ID
* @param string $group The cache data group
*
* @return mixed Boolean false on no result, cached object otherwise
*
* @since 1.7.0
*/
public function get($id, $group = null)
{
$data = $this->cache->get($id, $group);
if ($data === false)
{
$locktest = $this->cache->lock($id, $group);
// If locklooped is true try to get the cached data again; it could exist now.
if ($locktest->locked === true && $locktest->locklooped === true)
{
$data = $this->cache->get($id, $group);
}
if ($locktest->locked === true)
{
$this->cache->unlock($id, $group);
}
}
// Check again because we might get it from second attempt
if ($data !== false)
{
// Trim to fix unserialize errors
$data = unserialize(trim($data));
}
return $data;
}
/**
* Store data to cache by ID and group
*
* @param mixed $data The data to store
* @param string $id The cache data ID
* @param string $group The cache data group
* @param boolean $wrkarounds True to use wrkarounds
*
* @return boolean True if cache stored
*
* @since 1.7.0
*/
public function store($data, $id, $group = null, $wrkarounds = true)
{
$locktest = $this->cache->lock($id, $group);
if ($locktest->locked === false && $locktest->locklooped === true)
{
// We can not store data because another process is in the middle of saving
return false;
}
$result = $this->cache->store(serialize($data), $id, $group);
if ($locktest->locked === true)
{
$this->cache->unlock($id, $group);
}
return $result;
}
}
CallbackController.php 0000604 00000013405 15245535226 0011025 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* Joomla! Cache callback type object
*
* @since 1.7.0
*/
class CallbackController extends CacheController
{
/**
* Executes a cacheable callback if not found in cache else returns cached output and result
*
* Since arguments to this function are read with func_get_args you can pass any number of arguments to this method
* as long as the first argument passed is the callback definition.
*
* The callback definition can be in several forms:
* - Standard PHP Callback array see <https://www.php.net/callback> [recommended]
* - Function name as a string eg. 'foo' for function foo()
* - Static method name as a string eg. 'MyClass::myMethod' for method myMethod() of class MyClass
*
* @return mixed Result of the callback
*
* @since 1.7.0
* @deprecated 4.0
*/
public function call()
{
// Get callback and arguments
$args = func_get_args();
$callback = array_shift($args);
return $this->get($callback, $args);
}
/**
* Executes a cacheable callback if not found in cache else returns cached output and result
*
* @param mixed $callback Callback or string shorthand for a callback
* @param array $args Callback arguments
* @param mixed $id Cache ID
* @param boolean $wrkarounds True to use workarounds
* @param array $woptions Workaround options
*
* @return mixed Result of the callback
*
* @since 1.7.0
*/
public function get($callback, $args = array(), $id = false, $wrkarounds = false, $woptions = array())
{
// Normalize callback
if (is_array($callback) || is_callable($callback))
{
// We have a standard php callback array -- do nothing
}
elseif (strstr($callback, '::'))
{
// This is shorthand for a static method callback classname::methodname
list ($class, $method) = explode('::', $callback);
$callback = array(trim($class), trim($method));
}
elseif (strstr($callback, '->'))
{
/*
* This is a really not so smart way of doing this... we provide this for backward compatibility but this
* WILL! disappear in a future version. If you are using this syntax change your code to use the standard
* PHP callback array syntax: <https://www.php.net/callback>
*
* We have to use some silly global notation to pull it off and this is very unreliable
*/
list ($object_123456789, $method) = explode('->', $callback);
global $$object_123456789;
$callback = array($$object_123456789, $method);
}
if (!$id)
{
// Generate an ID
$id = $this->_makeId($callback, $args);
}
$data = $this->cache->get($id);
$locktest = (object) array('locked' => null, 'locklooped' => null);
if ($data === false)
{
$locktest = $this->cache->lock($id);
// If locklooped is true try to get the cached data again; it could exist now.
if ($locktest->locked === true && $locktest->locklooped === true)
{
$data = $this->cache->get($id);
}
}
if ($data !== false)
{
if ($locktest->locked === true)
{
$this->cache->unlock($id);
}
$data = unserialize(trim($data));
if ($wrkarounds)
{
echo Cache::getWorkarounds(
$data['output'],
array('mergehead' => isset($woptions['mergehead']) ? $woptions['mergehead'] : 0)
);
}
else
{
echo $data['output'];
}
return $data['result'];
}
if (!is_array($args))
{
$referenceArgs = !empty($args) ? array(&$args) : array();
}
else
{
$referenceArgs = &$args;
}
if ($locktest->locked === false && $locktest->locklooped === true)
{
// We can not store data because another process is in the middle of saving
return call_user_func_array($callback, $referenceArgs);
}
$coptions = array();
if (isset($woptions['modulemode']) && $woptions['modulemode'] == 1)
{
$document = \JFactory::getDocument();
if (method_exists($document, 'getHeadData'))
{
$coptions['headerbefore'] = $document->getHeadData();
}
$coptions['modulemode'] = 1;
}
else
{
$coptions['modulemode'] = 0;
}
$coptions['nopathway'] = isset($woptions['nopathway']) ? $woptions['nopathway'] : 1;
$coptions['nohead'] = isset($woptions['nohead']) ? $woptions['nohead'] : 1;
$coptions['nomodules'] = isset($woptions['nomodules']) ? $woptions['nomodules'] : 1;
ob_start();
ob_implicit_flush(false);
$result = call_user_func_array($callback, $referenceArgs);
$output = ob_get_clean();
$data = array('result' => $result);
if ($wrkarounds)
{
$data['output'] = Cache::setWorkarounds($output, $coptions);
}
else
{
$data['output'] = $output;
}
// Store the cache data
$this->cache->store(serialize($data), $id);
if ($locktest->locked === true)
{
$this->cache->unlock($id);
}
echo $output;
return $result;
}
/**
* Generate a callback cache ID
*
* @param callback $callback Callback to cache
* @param array $args Arguments to the callback method to cache
*
* @return string MD5 Hash
*
* @since 1.7.0
*/
protected function _makeId($callback, $args)
{
if (is_array($callback) && is_object($callback[0]))
{
$vars = get_object_vars($callback[0]);
$vars[] = strtolower(get_class($callback[0]));
$callback[0] = $vars;
}
// A Closure can't be serialized, so to generate the ID we'll need to get its hash
if (is_a($callback, 'closure'))
{
$hash = spl_object_hash($callback);
return md5($hash . serialize(array($args)));
}
return md5(serialize(array($callback, $args)));
}
}
PageController.php 0000604 00000011067 15245535226 0010207 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* Joomla! Cache page type object
*
* @since 1.7.0
*/
class PageController extends CacheController
{
/**
* ID property for the cache page object.
*
* @var integer
* @since 1.7.0
*/
protected $_id;
/**
* Cache group
*
* @var string
* @since 1.7.0
*/
protected $_group;
/**
* Cache lock test
*
* @var \stdClass
* @since 1.7.0
*/
protected $_locktest = null;
/**
* Get the cached page data
*
* @param boolean $id The cache data ID
* @param string $group The cache data group
*
* @return mixed Boolean false on no result, cached object otherwise
*
* @since 1.7.0
*/
public function get($id = false, $group = 'page')
{
// If an id is not given, generate it from the request
if (!$id)
{
$id = $this->_makeId();
}
// If the etag matches the page id ... set a no change header and exit : utilize browser cache
if (!headers_sent() && isset($_SERVER['HTTP_IF_NONE_MATCH']))
{
$etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
if ($etag == $id)
{
$browserCache = isset($this->options['browsercache']) ? $this->options['browsercache'] : false;
if ($browserCache)
{
$this->_noChange();
}
}
}
// We got a cache hit... set the etag header and echo the page data
$data = $this->cache->get($id, $group);
$this->_locktest = (object) array('locked' => null, 'locklooped' => null);
if ($data === false)
{
$this->_locktest = $this->cache->lock($id, $group);
// If locklooped is true try to get the cached data again; it could exist now.
if ($this->_locktest->locked === true && $this->_locktest->locklooped === true)
{
$data = $this->cache->get($id, $group);
}
}
if ($data !== false)
{
if ($this->_locktest->locked === true)
{
$this->cache->unlock($id, $group);
}
$data = unserialize(trim($data));
$data = Cache::getWorkarounds($data);
$this->_setEtag($id);
return $data;
}
// Set ID and group placeholders
$this->_id = $id;
$this->_group = $group;
return false;
}
/**
* Stop the cache buffer and store the cached data
*
* @param mixed $data The data to store
* @param string $id The cache data ID
* @param string $group The cache data group
* @param boolean $wrkarounds True to use workarounds
*
* @return boolean
*
* @since 1.7.0
*/
public function store($data, $id, $group = null, $wrkarounds = true)
{
if ($this->_locktest->locked === false && $this->_locktest->locklooped === true)
{
// We can not store data because another process is in the middle of saving
return false;
}
// Get page data from the application object
if (!$data)
{
$data = \JFactory::getApplication()->getBody();
// Only attempt to store if page data exists.
if (!$data)
{
return false;
}
}
// Get id and group and reset the placeholders
if (!$id)
{
$id = $this->_id;
}
if (!$group)
{
$group = $this->_group;
}
if ($wrkarounds)
{
$data = Cache::setWorkarounds(
$data,
array(
'nopathway' => 1,
'nohead' => 1,
'nomodules' => 1,
'headers' => true,
)
);
}
$result = $this->cache->store(serialize($data), $id, $group);
if ($this->_locktest->locked === true)
{
$this->cache->unlock($id, $group);
}
return $result;
}
/**
* Generate a page cache id
*
* @return string MD5 Hash
*
* @since 1.7.0
* @todo Discuss whether this should be coupled to a data hash or a request hash ... perhaps hashed with a serialized request
*/
protected function _makeId()
{
return Cache::makeId();
}
/**
* There is no change in page data so send an unmodified header and die gracefully
*
* @return void
*
* @since 1.7.0
*/
protected function _noChange()
{
$app = \JFactory::getApplication();
// Send not modified header and exit gracefully
$app->setHeader('Status', 304, true);
$app->sendHeaders();
$app->close();
}
/**
* Set the ETag header in the response
*
* @param string $etag The entity tag (etag) to set
*
* @return void
*
* @since 1.7.0
*/
protected function _setEtag($etag)
{
\JFactory::getApplication()->setHeader('ETag', '"' . $etag . '"', true);
}
}
ViewController.php 0000604 00000006414 15245535226 0010245 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Cache\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;
/**
* Joomla! Cache view type object
*
* @since 1.7.0
*/
class ViewController extends CacheController
{
/**
* Get the cached view data
*
* @param object $view The view object to cache output for
* @param string $method The method name of the view method to cache output for
* @param mixed $id The cache data ID
* @param boolean $wrkarounds True to enable workarounds.
*
* @return boolean True if the cache is hit (false else)
*
* @since 1.7.0
*/
public function get($view, $method = 'display', $id = false, $wrkarounds = true)
{
// If an id is not given generate it from the request
if (!$id)
{
$id = $this->_makeId($view, $method);
}
$data = $this->cache->get($id);
$locktest = (object) array('locked' => null, 'locklooped' => null);
if ($data === false)
{
$locktest = $this->cache->lock($id);
/*
* If the loop is completed and returned true it means the lock has been set.
* If looped is true try to get the cached data again; it could exist now.
*/
if ($locktest->locked === true && $locktest->locklooped === true)
{
$data = $this->cache->get($id);
}
// False means that locking is either turned off or maxtime has been exceeded. Execute the view.
}
if ($data !== false)
{
if ($locktest->locked === true)
{
$this->cache->unlock($id);
}
$data = unserialize(trim($data));
if ($wrkarounds)
{
echo Cache::getWorkarounds($data);
}
else
{
// No workarounds, so all data is stored in one piece
echo $data;
}
return true;
}
// No hit so we have to execute the view
if (!method_exists($view, $method))
{
return false;
}
if ($locktest->locked === false && $locktest->locklooped === true)
{
// We can not store data because another process is in the middle of saving
$view->$method();
return false;
}
// Capture and echo output
ob_start();
ob_implicit_flush(false);
$view->$method();
$data = ob_get_clean();
echo $data;
/*
* For a view we have a special case. We need to cache not only the output from the view, but the state
* of the document head after the view has been rendered. This will allow us to properly cache any attached
* scripts or stylesheets or links or any other modifications that the view has made to the document object
*/
if ($wrkarounds)
{
$data = Cache::setWorkarounds($data);
}
// Store the cache data
$this->cache->store(serialize($data), $id);
if ($locktest->locked === true)
{
$this->cache->unlock($id);
}
return false;
}
/**
* Generate a view cache ID.
*
* @param object $view The view object to cache output for
* @param string $method The method name to cache for the view object
*
* @return string MD5 Hash
*
* @since 1.7.0
*/
protected function _makeId($view, $method)
{
return md5(serialize(array(Cache::makeId(), get_class($view), $method)));
}
}
FormController.php 0000604 00000062453 15245611237 0010240 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
/**
* Controller tailored to suit most form-based admin operations.
*
* @since 1.6
* @todo Add ability to set redirect manually to better cope with frontend usage.
*/
class FormController extends BaseController
{
/**
* The context for storing internal data, e.g. record.
*
* @var string
* @since 1.6
*/
protected $context;
/**
* The URL option for the component.
*
* @var string
* @since 1.6
*/
protected $option;
/**
* The URL view item variable.
*
* @var string
* @since 1.6
*/
protected $view_item;
/**
* The URL view list variable.
*
* @var string
* @since 1.6
*/
protected $view_list;
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param MVCFactoryInterface $factory The factory.
*
* @see \JControllerLegacy
* @since 1.6
* @throws \Exception
*/
public function __construct($config = array(), MVCFactoryInterface $factory = null)
{
parent::__construct($config, $factory);
// Guess the option as com_NameOfController
if (empty($this->option))
{
$this->option = 'com_' . strtolower($this->getName());
}
// Guess the \JText message prefix. Defaults to the option.
if (empty($this->text_prefix))
{
$this->text_prefix = strtoupper($this->option);
}
// Guess the context as the suffix, eg: OptionControllerContent.
if (empty($this->context))
{
$r = null;
if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r))
{
throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
}
$this->context = strtolower($r[2]);
}
// Guess the item view as the context.
if (empty($this->view_item))
{
$this->view_item = $this->context;
}
// Guess the list view as the plural of the item view.
if (empty($this->view_list))
{
// @TODO Probably worth moving to an inflector class based on
// http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/
// Simple pluralisation based on public domain snippet by Paul Osman
// For more complex types, just manually set the variable in your class.
$plural = array(
array('/(x|ch|ss|sh)$/i', "$1es"),
array('/([^aeiouy]|qu)y$/i', "$1ies"),
array('/([^aeiouy]|qu)ies$/i', "$1y"),
array('/(bu)s$/i', "$1ses"),
array('/s$/i', 's'),
array('/$/', 's'),
);
// Check for matches using regular expressions
foreach ($plural as $pattern)
{
if (preg_match($pattern[0], $this->view_item))
{
$this->view_list = preg_replace($pattern[0], $pattern[1], $this->view_item);
break;
}
}
}
// Apply, Save & New, and Save As copy should be standard on forms.
$this->registerTask('apply', 'save');
$this->registerTask('save2new', 'save');
$this->registerTask('save2copy', 'save');
$this->registerTask('editAssociations', 'save');
}
/**
* Method to add a new record.
*
* @return boolean True if the record can be added, false if not.
*
* @since 1.6
*/
public function add()
{
$context = "$this->option.edit.$this->context";
// Access check.
if (!$this->allowAdd())
{
// Set the internal error and also the redirect error.
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Clear the record edit information from the session.
\JFactory::getApplication()->setUserState($context . '.data', null);
// Redirect to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend(), false
)
);
return true;
}
/**
* Method to check if you can add a new record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
*
* @return boolean
*
* @since 1.6
*/
protected function allowAdd($data = array())
{
$user = \JFactory::getUser();
return $user->authorise('core.create', $this->option) || count($user->getAuthorisedCategories($this->option, 'core.create'));
}
/**
* Method to check if you can edit an existing record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
* @param string $key The name of the key for the primary key; default is id.
*
* @return boolean
*
* @since 1.6
*/
protected function allowEdit($data = array(), $key = 'id')
{
return \JFactory::getUser()->authorise('core.edit', $this->option);
}
/**
* Method to check if you can save a new or existing record.
*
* Extended classes can override this if necessary.
*
* @param array $data An array of input data.
* @param string $key The name of the key for the primary key.
*
* @return boolean
*
* @since 1.6
*/
protected function allowSave($data, $key = 'id')
{
$recordId = isset($data[$key]) ? $data[$key] : '0';
if ($recordId)
{
return $this->allowEdit($data, $key);
}
else
{
return $this->allowAdd($data);
}
}
/**
* Method to run batch operations.
*
* @param \JModelLegacy $model The model of the component being processed.
*
* @return boolean True if successful, false otherwise and internal error is set.
*
* @since 1.7
*/
public function batch($model)
{
$vars = $this->input->post->get('batch', array(), 'array');
$cid = (array) $this->input->post->get('cid', array(), 'int');
// Remove zero values resulting from input filter
$cid = array_filter($cid);
// Build an array of item contexts to check
$contexts = array();
$option = isset($this->extension) ? $this->extension : $this->option;
foreach ($cid as $id)
{
// If we're coming from com_categories, we need to use extension vs. option
$contexts[$id] = $option . '.' . $this->context . '.' . $id;
}
// Attempt to run the batch operation.
if ($model->batch($vars, $cid, $contexts))
{
$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_BATCH'));
return true;
}
else
{
$this->setMessage(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_FAILED', $model->getError()), 'warning');
return false;
}
}
/**
* Method to cancel an edit.
*
* @param string $key The name of the primary key of the URL variable.
*
* @return boolean True if access level checks pass, false otherwise.
*
* @since 1.6
*/
public function cancel($key = null)
{
$this->checkToken();
$model = $this->getModel();
$table = $model->getTable();
$context = "$this->option.edit.$this->context";
if (empty($key))
{
$key = $table->getKeyName();
}
$recordId = $this->input->getInt($key);
// Attempt to check-in the current record.
if ($recordId && property_exists($table, 'checked_out') && $model->checkin($recordId) === false)
{
// Check-in failed, go back to the record and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $key), false
)
);
return false;
}
// Clean the session data and redirect.
$this->releaseEditId($context, $recordId);
\JFactory::getApplication()->setUserState($context . '.data', null);
$url = 'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend();
// Check if there is a return value
$return = $this->input->get('return', null, 'base64');
if (!is_null($return) && \JUri::isInternal(base64_decode($return)))
{
$url = base64_decode($return);
}
// Redirect to the list screen.
$this->setRedirect(\JRoute::_($url, false));
return true;
}
/**
* Method to edit an existing record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key
* (sometimes required to avoid router collisions).
*
* @return boolean True if access level check and checkout passes, false otherwise.
*
* @since 1.6
*/
public function edit($key = null, $urlVar = null)
{
// Do not cache the response to this, its a redirect, and mod_expires and google chrome browser bugs cache it forever!
\JFactory::getApplication()->allowCache(false);
$model = $this->getModel();
$table = $model->getTable();
$cid = (array) $this->input->post->get('cid', array(), 'int');
$context = "$this->option.edit.$this->context";
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary key.
if (empty($urlVar))
{
$urlVar = $key;
}
// Get the previous record id (if any) and the current record id.
$recordId = (int) (count($cid) ? $cid[0] : $this->input->getInt($urlVar));
$checkin = property_exists($table, $table->getColumnAlias('checked_out'));
// Access check.
if (!$this->allowEdit(array($key => $recordId), $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Attempt to check-out the new record for editing and redirect.
if ($checkin && !$model->checkout($recordId))
{
// Check-out failed, display a notice but allow the user to see the record.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
else
{
// Check-out succeeded, push the new record id into the session.
$this->holdEditId($context, $recordId);
\JFactory::getApplication()->setUserState($context . '.data', null);
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return true;
}
}
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \JModelLegacy The model.
*
* @since 1.6
*/
public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true))
{
if (empty($name))
{
$name = $this->context;
}
return parent::getModel($name, $prefix, $config);
}
/**
* Gets the URL arguments to append to an item redirect.
*
* @param integer $recordId The primary key id for the item.
* @param string $urlVar The name of the URL variable for the id.
*
* @return string The arguments to append to the redirect URL.
*
* @since 1.6
*/
protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
{
$append = '';
// Setup redirect info.
if ($tmpl = $this->input->get('tmpl', '', 'string'))
{
$append .= '&tmpl=' . $tmpl;
}
if ($layout = $this->input->get('layout', 'edit', 'string'))
{
$append .= '&layout=' . $layout;
}
if ($forcedLanguage = $this->input->get('forcedLanguage', '', 'cmd'))
{
$append .= '&forcedLanguage=' . $forcedLanguage;
}
if ($recordId)
{
$append .= '&' . $urlVar . '=' . $recordId;
}
$return = $this->input->get('return', null, 'base64');
if ($return)
{
$append .= '&return=' . $return;
}
return $append;
}
/**
* Gets the URL arguments to append to a list redirect.
*
* @return string The arguments to append to the redirect URL.
*
* @since 1.6
*/
protected function getRedirectToListAppend()
{
$append = '';
// Setup redirect info.
if ($tmpl = $this->input->get('tmpl', '', 'string'))
{
$append .= '&tmpl=' . $tmpl;
}
if ($forcedLanguage = $this->input->get('forcedLanguage', '', 'cmd'))
{
$append .= '&forcedLanguage=' . $forcedLanguage;
}
return $append;
}
/**
* Function that allows child controller access to model data
* after the data has been saved.
*
* @param \JModelLegacy $model The data model object.
* @param array $validData The validated data.
*
* @return void
*
* @since 1.6
*/
protected function postSaveHook(\JModelLegacy $model, $validData = array())
{
}
/**
* Method to load a row from version history
*
* @return mixed True if the record can be added, an error object if not.
*
* @since 3.2
*/
public function loadhistory()
{
$model = $this->getModel();
$table = $model->getTable();
$historyId = $this->input->getInt('version_id', null);
if (!$model->loadhistory($historyId, $table))
{
$this->setMessage($model->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
$recordId = $table->$key;
// To avoid data collisions the urlVar may be different from the primary key.
$urlVar = empty($this->urlVar) ? $key : $this->urlVar;
// Access check.
if (!$this->allowEdit(array($key => $recordId), $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
$table->checkin();
return false;
}
$table->store();
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
$this->setMessage(
\JText::sprintf(
'JLIB_APPLICATION_SUCCESS_LOAD_HISTORY', $model->getState('save_date'), $model->getState('version_note')
)
);
// Invoke the postSave method to allow for the child class to access the model.
$this->postSaveHook($model);
return true;
}
/**
* Method to save a record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
*
* @return boolean True if successful, false otherwise.
*
* @since 1.6
*/
public function save($key = null, $urlVar = null)
{
// Check for request forgeries.
$this->checkToken();
$app = \JFactory::getApplication();
$model = $this->getModel();
$table = $model->getTable();
$data = $this->input->post->get('jform', array(), 'array');
$checkin = property_exists($table, $table->getColumnAlias('checked_out'));
$context = "$this->option.edit.$this->context";
$task = $this->getTask();
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $table->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary key.
if (empty($urlVar))
{
$urlVar = $key;
}
$recordId = $this->input->getInt($urlVar);
// Populate the row id from the session.
$data[$key] = $recordId;
// The save2copy task needs to be handled slightly differently.
if ($task === 'save2copy')
{
// Check-in the original row.
if ($checkin && $model->checkin($data[$key]) === false)
{
// Check-in failed. Go back to the item and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
// Reset the ID, the multilingual associations and then treat the request as for Apply.
$data[$key] = 0;
$data['associations'] = array();
$task = 'apply';
}
// Access check.
if (!$this->allowSave($data, $key))
{
$this->setError(\JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
return false;
}
// Validate the posted data.
// Sometimes the form needs some posted data, such as for plugins and modules.
$form = $model->getForm($data, false);
if (!$form)
{
$app->enqueueMessage($model->getError(), 'error');
return false;
}
// Send an object which can be modified through the plugin event
$objData = (object) $data;
$app->triggerEvent(
'onContentNormaliseRequestData',
array($this->option . '.' . $this->context, $objData, $form)
);
$data = (array) $objData;
// Test whether the data is valid.
$validData = $model->validate($form, $data);
// Check for validation errors.
if ($validData === false)
{
// Get the validation messages.
$errors = $model->getErrors();
// Push up to three validation messages out to the user.
for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
{
if ($errors[$i] instanceof \Exception)
{
$app->enqueueMessage($errors[$i]->getMessage(), 'warning');
}
else
{
$app->enqueueMessage($errors[$i], 'warning');
}
}
/**
* We need the filtered value of calendar fields because the UTC normalision is
* done in the filter and on output. This would apply the Timezone offset on
* reload. We set the calendar values we save to the processed date.
*/
$filteredData = $form->filter($data);
foreach ($form->getFieldset() as $field)
{
if ($field->type === 'Calendar')
{
$fieldName = $field->fieldname;
if (isset($filteredData[$fieldName]))
{
$data[$fieldName] = $filteredData[$fieldName];
}
}
}
// Save the data in the session.
$app->setUserState($context . '.data', $data);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
if (!isset($validData['tags']))
{
$validData['tags'] = null;
}
// Attempt to save the data.
if (!$model->save($validData))
{
// Save the data in the session.
$app->setUserState($context . '.data', $validData);
// Redirect back to the edit screen.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
// Save succeeded, so check-in the record.
if ($checkin && $model->checkin($validData[$key]) === false)
{
// Save the data in the session.
$app->setUserState($context . '.data', $validData);
// Check-in failed, so go back to the record and display a notice.
$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
return false;
}
$langKey = $this->text_prefix . ($recordId === 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS';
$prefix = \JFactory::getLanguage()->hasKey($langKey) ? $this->text_prefix : 'JLIB_APPLICATION';
$this->setMessage(\JText::_($prefix . ($recordId === 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'));
// Redirect the user and adjust session state based on the chosen task.
switch ($task)
{
case 'apply':
// Set the record data in the session.
$recordId = $model->getState($this->context . '.id');
$this->holdEditId($context, $recordId);
$app->setUserState($context . '.data', null);
$model->checkout($recordId);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend($recordId, $urlVar), false
)
);
break;
case 'save2new':
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$app->setUserState($context . '.data', null);
// Redirect back to the edit screen.
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item
. $this->getRedirectToItemAppend(null, $urlVar), false
)
);
break;
default:
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$app->setUserState($context . '.data', null);
$url = 'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend();
// Check if there is a return value
$return = $this->input->get('return', null, 'base64');
if (!is_null($return) && \JUri::isInternal(base64_decode($return)))
{
$url = base64_decode($return);
}
// Redirect to the list screen.
$this->setRedirect(\JRoute::_($url, false));
break;
}
// Invoke the postSave method to allow for the child class to access the model.
$this->postSaveHook($model, $validData);
return true;
}
/**
* Method to reload a record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
*
* @return void
*
* @since 3.7.4
*/
public function reload($key = null, $urlVar = null)
{
// Check for request forgeries.
$this->checkToken();
$app = \JFactory::getApplication();
$model = $this->getModel();
$data = $this->input->post->get('jform', array(), 'array');
// Determine the name of the primary key for the data.
if (empty($key))
{
$key = $model->getTable()->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary key.
if (empty($urlVar))
{
$urlVar = $key;
}
$recordId = $this->input->getInt($urlVar);
// Populate the row id from the session.
$data[$key] = $recordId;
// Check if it is allowed to edit or create the data
if (($recordId && !$this->allowEdit($data, $key)) || (!$recordId && !$this->allowAdd($data)))
{
$this->setRedirect(
\JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_list
. $this->getRedirectToListAppend(), false
)
);
$this->redirect();
}
// The redirect url
$redirectUrl = \JRoute::_(
'index.php?option=' . $this->option . '&view=' . $this->view_item .
$this->getRedirectToItemAppend($recordId, $urlVar),
false
);
/* @var \JForm $form */
$form = $model->getForm($data, false);
/**
* We need the filtered value of calendar fields because the UTC normalision is
* done in the filter and on output. This would apply the Timezone offset on
* reload. We set the calendar values we save to the processed date.
*/
$filteredData = $form->filter($data);
foreach ($form->getFieldset() as $field)
{
if ($field->type === 'Calendar')
{
$fieldName = $field->fieldname;
if ($field->group)
{
if (isset($filteredData[$field->group][$fieldName]))
{
$data[$field->group][$fieldName] = $filteredData[$field->group][$fieldName];
}
}
else
{
if (isset($filteredData[$fieldName]))
{
$data[$fieldName] = $filteredData[$fieldName];
}
}
}
}
// Save the data in the session.
$app->setUserState($this->option . '.edit.' . $this->context . '.data', $data);
$this->setRedirect($redirectUrl);
$this->redirect();
}
/**
* Load item to edit associations in com_associations
*
* @return void
*
* @since 3.9.0
*
* @deprecated 5.0 It is handled by regular save method now.
*/
public function editAssociations()
{
// Initialise variables.
$app = \JFactory::getApplication();
$input = $app->input;
$model = $this->getModel();
$data = $input->get('jform', array(), 'array');
$model->editAssociations($data);
}
}
BaseController.php 0000604 00000062117 15245611237 0010204 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
use Joomla\CMS\MVC\Factory\LegacyFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
defined('JPATH_PLATFORM') or die;
/**
* Base class for a Joomla Controller
*
* Controller (Controllers are where you put all the actual code.) Provides basic
* functionality, such as rendering views (aka displaying templates).
*
* @since 2.5.5
*/
class BaseController extends \JObject
{
/**
* The base path of the controller
*
* @var string
* @since 3.0
*/
protected $basePath;
/**
* The default view for the display method.
*
* @var string
* @since 3.0
*/
protected $default_view;
/**
* The mapped task that was performed.
*
* @var string
* @since 3.0
*/
protected $doTask;
/**
* Redirect message.
*
* @var string
* @since 3.0
*/
protected $message;
/**
* Redirect message type.
*
* @var string
* @since 3.0
*/
protected $messageType;
/**
* Array of class methods
*
* @var array
* @since 3.0
*/
protected $methods;
/**
* The name of the controller
*
* @var array
* @since 3.0
*/
protected $name;
/**
* The prefix of the models
*
* @var string
* @since 3.0
*/
protected $model_prefix;
/**
* The set of search directories for resources (views).
*
* @var array
* @since 3.0
*/
protected $paths;
/**
* URL for redirection.
*
* @var string
* @since 3.0
*/
protected $redirect;
/**
* Current or most recently performed task.
*
* @var string
* @since 3.0
*/
protected $task;
/**
* Array of class methods to call for a given task.
*
* @var array
* @since 3.0
*/
protected $taskMap;
/**
* Hold a JInput object for easier access to the input variables.
*
* @var \JInput
* @since 3.0
*/
protected $input;
/**
* The factory.
*
* @var MVCFactoryInterface
* @since 3.10.0
*/
protected $factory;
/**
* Instance container.
*
* @var \JControllerLegacy
* @since 3.0
*/
protected static $instance;
/**
* Instance container containing the views.
*
* @var \JViewLegacy[]
* @since 3.4
*/
protected static $views;
/**
* Adds to the stack of model paths in LIFO order.
*
* @param mixed $path The directory (string), or list of directories (array) to add.
* @param string $prefix A prefix for models
*
* @return void
*
* @since 3.0
*/
public static function addModelPath($path, $prefix = '')
{
\JModelLegacy::addIncludePath($path, $prefix);
}
/**
* Create the filename for a resource.
*
* @param string $type The resource type to create the filename for.
* @param array $parts An associative array of filename information. Optional.
*
* @return string The filename.
*
* @since 3.0
*/
public static function createFileName($type, $parts = array())
{
$filename = '';
switch ($type)
{
case 'controller':
if (!empty($parts['format']))
{
if ($parts['format'] === 'html')
{
$parts['format'] = '';
}
else
{
$parts['format'] = '.' . $parts['format'];
}
}
else
{
$parts['format'] = '';
}
$filename = strtolower($parts['name'] . $parts['format'] . '.php');
break;
case 'view':
if (!empty($parts['type']))
{
$parts['type'] = '.' . $parts['type'];
}
else
{
$parts['type'] = '';
}
$filename = strtolower($parts['name'] . '/view' . $parts['type'] . '.php');
break;
}
return $filename;
}
/**
* Method to get a singleton controller instance.
*
* @param string $prefix The prefix for the controller.
* @param array $config An array of optional constructor options.
*
* @return \JControllerLegacy
*
* @since 3.0
* @throws \Exception if the controller cannot be loaded.
*/
public static function getInstance($prefix, $config = array())
{
if (is_object(self::$instance))
{
return self::$instance;
}
$input = \JFactory::getApplication()->input;
// Get the environment configuration.
$basePath = array_key_exists('base_path', $config) ? $config['base_path'] : JPATH_COMPONENT;
$format = $input->getWord('format');
$command = $input->get('task', 'display');
// Check for array format.
$filter = \JFilterInput::getInstance();
if (is_array($command))
{
$command = $filter->clean(array_pop(array_keys($command)), 'cmd');
}
else
{
$command = $filter->clean($command, 'cmd');
}
// Check for a controller.task command.
if (strpos($command, '.') !== false)
{
// Explode the controller.task command.
list ($type, $task) = explode('.', $command);
// Define the controller filename and path.
$file = self::createFileName('controller', array('name' => $type, 'format' => $format));
$path = $basePath . '/controllers/' . $file;
$backuppath = $basePath . '/controller/' . $file;
// Reset the task without the controller context.
$input->set('task', $task);
}
else
{
// Base controller.
$type = '';
// Define the controller filename and path.
$file = self::createFileName('controller', array('name' => 'controller', 'format' => $format));
$path = $basePath . '/' . $file;
$backupfile = self::createFileName('controller', array('name' => 'controller'));
$backuppath = $basePath . '/' . $backupfile;
}
// Get the controller class name.
$class = ucfirst($prefix) . 'Controller' . ucfirst($type);
// Include the class if not present.
if (!class_exists($class))
{
// If the controller file path exists, include it.
if (file_exists($path))
{
require_once $path;
}
elseif (isset($backuppath) && file_exists($backuppath))
{
require_once $backuppath;
}
else
{
throw new \InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER', $type, $format));
}
}
// Instantiate the class.
if (!class_exists($class))
{
throw new \InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER_CLASS', $class));
}
// Instantiate the class, store it to the static container, and return it
return self::$instance = new $class($config);
}
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* Recognized key values include 'name', 'default_task', 'model_path', and
* 'view_path' (this list is not meant to be comprehensive).
* @param MVCFactoryInterface $factory The factory.
*
* @since 3.0
*/
public function __construct($config = array(), MVCFactoryInterface $factory = null)
{
$this->methods = array();
$this->message = null;
$this->messageType = 'message';
$this->paths = array();
$this->redirect = null;
$this->taskMap = array();
if (defined('JDEBUG') && JDEBUG)
{
\JLog::addLogger(array('text_file' => 'jcontroller.log.php'), \JLog::ALL, array('controller'));
}
$this->input = \JFactory::getApplication()->input;
// Determine the methods to exclude from the base class.
$xMethods = get_class_methods('\JControllerLegacy');
// Get the public methods in this class using reflection.
$r = new \ReflectionClass($this);
$rMethods = $r->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($rMethods as $rMethod)
{
$mName = $rMethod->getName();
// Add default display method if not explicitly declared.
if ($mName === 'display' || !in_array($mName, $xMethods))
{
$this->methods[] = strtolower($mName);
// Auto register the methods as tasks.
$this->taskMap[strtolower($mName)] = $mName;
}
}
// Set the view name
if (empty($this->name))
{
if (array_key_exists('name', $config))
{
$this->name = $config['name'];
}
else
{
$this->name = $this->getName();
}
}
// Set a base path for use by the controller
if (array_key_exists('base_path', $config))
{
$this->basePath = $config['base_path'];
}
else
{
$this->basePath = JPATH_COMPONENT;
}
// If the default task is set, register it as such
if (array_key_exists('default_task', $config))
{
$this->registerDefaultTask($config['default_task']);
}
else
{
$this->registerDefaultTask('display');
}
// Set the models prefix
if (empty($this->model_prefix))
{
if (array_key_exists('model_prefix', $config))
{
// User-defined prefix
$this->model_prefix = $config['model_prefix'];
}
else
{
$this->model_prefix = ucfirst($this->name) . 'Model';
}
}
// Set the default model search path
if (array_key_exists('model_path', $config))
{
// User-defined dirs
$this->addModelPath($config['model_path'], $this->model_prefix);
}
else
{
$this->addModelPath($this->basePath . '/models', $this->model_prefix);
}
// Set the default view search path
if (array_key_exists('view_path', $config))
{
// User-defined dirs
$this->setPath('view', $config['view_path']);
}
else
{
$this->setPath('view', $this->basePath . '/views');
}
// Set the default view.
if (array_key_exists('default_view', $config))
{
$this->default_view = $config['default_view'];
}
elseif (empty($this->default_view))
{
$this->default_view = $this->getName();
}
$this->factory = $factory ? : new LegacyFactory;
}
/**
* Adds to the search path for templates and resources.
*
* @param string $type The path type (e.g. 'model', 'view').
* @param mixed $path The directory string or stream array to search.
*
* @return \JControllerLegacy A \JControllerLegacy object to support chaining.
*
* @since 3.0
*/
protected function addPath($type, $path)
{
if (!isset($this->paths[$type]))
{
$this->paths[$type] = array();
}
// Loop through the path directories
foreach ((array) $path as $dir)
{
// No surrounding spaces allowed!
$dir = rtrim(\JPath::check($dir), '/') . '/';
// Add to the top of the search dirs
array_unshift($this->paths[$type], $dir);
}
return $this;
}
/**
* Add one or more view paths to the controller's stack, in LIFO order.
*
* @param mixed $path The directory (string) or list of directories (array) to add.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function addViewPath($path)
{
return $this->addPath('view', $path);
}
/**
* Authorisation check
*
* @param string $task The ACO Section Value to check access on.
*
* @return boolean True if authorised
*
* @since 3.0
* @deprecated 3.0 Use \JAccess instead.
*/
public function authorise($task)
{
\JLog::add(__METHOD__ . ' is deprecated. Use \JAccess instead.', \JLog::WARNING, 'deprecated');
return true;
}
/**
* Method to check whether an ID is in the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit list.
*
* @return boolean True if the ID is in the edit list.
*
* @since 3.0
*/
protected function checkEditId($context, $id)
{
if ($id)
{
$values = (array) \JFactory::getApplication()->getUserState($context . '.id');
$result = in_array((int) $id, $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Checking edit ID %s.%s: %d %s',
$context,
$id,
(int) $result,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
return $result;
}
// No id for a new item.
return true;
}
/**
* Method to load and return a model object.
*
* @param string $name The name of the model.
* @param string $prefix Optional model prefix.
* @param array $config Configuration array for the model. Optional.
*
* @return \JModelLegacy|boolean Model object on success; otherwise false on failure.
*
* @since 3.0
*/
protected function createModel($name, $prefix = '', $config = array())
{
$model = $this->factory->createModel($name, $prefix, $config);
if ($model === null)
{
return false;
}
return $model;
}
/**
* Method to load and return a view object. This method first looks in the
* current template directory for a match and, failing that, uses a default
* set path to load the view class file.
*
* Note the "name, prefix, type" order of parameters, which differs from the
* "name, type, prefix" order used in related public methods.
*
* @param string $name The name of the view.
* @param string $prefix Optional prefix for the view class name.
* @param string $type The type of view.
* @param array $config Configuration array for the view. Optional.
*
* @return \JViewLegacy|null View object on success; null or error result on failure.
*
* @since 3.0
* @throws \Exception
*/
protected function createView($name, $prefix = '', $type = '', $config = array())
{
$config['paths'] = $this->paths['view'];
return $this->factory->createView($name, $prefix, $type, $config);
}
/**
* Typical view method for MVC based architecture
*
* This function is provide as a default implementation, in most cases
* you will need to override it in your own controllers.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
*
* @return \JControllerLegacy A \JControllerLegacy object to support chaining.
*
* @since 3.0
*/
public function display($cachable = false, $urlparams = array())
{
$document = \JFactory::getDocument();
$viewType = $document->getType();
$viewName = $this->input->get('view', $this->default_view);
$viewLayout = $this->input->get('layout', 'default', 'string');
$view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));
// Get/Create the model
if ($model = $this->getModel($viewName))
{
// Push the model into the view (as default)
$view->setModel($model, true);
}
$view->document = $document;
// Display the view
if ($cachable && $viewType !== 'feed' && \JFactory::getConfig()->get('caching') >= 1)
{
$option = $this->input->get('option');
if (is_array($urlparams))
{
$app = \JFactory::getApplication();
if (!empty($app->registeredurlparams))
{
$registeredurlparams = $app->registeredurlparams;
}
else
{
$registeredurlparams = new \stdClass;
}
foreach ($urlparams as $key => $value)
{
// Add your safe URL parameters with variable type as value {@see \JFilterInput::clean()}.
$registeredurlparams->$key = $value;
}
$app->registeredurlparams = $registeredurlparams;
}
try
{
/** @var \JCacheControllerView $cache */
$cache = \JFactory::getCache($option, 'view');
$cache->get($view, 'display');
}
catch (\JCacheException $exception)
{
$view->display();
}
}
else
{
$view->display();
}
return $this;
}
/**
* Execute a task by triggering a method in the derived class.
*
* @param string $task The task to perform. If no matching task is found, the '__default' task is executed, if defined.
*
* @return mixed The value returned by the called method.
*
* @since 3.0
* @throws \Exception
*/
public function execute($task)
{
$this->task = $task;
$task = strtolower((string) $task);
if (isset($this->taskMap[$task]))
{
$doTask = $this->taskMap[$task];
}
elseif (isset($this->taskMap['__default']))
{
$doTask = $this->taskMap['__default'];
}
else
{
throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
}
// Record the actual task being fired
$this->doTask = $doTask;
return $this->$doTask();
}
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \JModelLegacy|boolean Model object on success; otherwise false on failure.
*
* @since 3.0
*/
public function getModel($name = '', $prefix = '', $config = array())
{
if (empty($name))
{
$name = $this->getName();
}
if (empty($prefix))
{
$prefix = $this->model_prefix;
}
if ($model = $this->createModel($name, $prefix, $config))
{
// Task is a reserved state
$model->setState('task', $this->task);
// Let's get the application object and set menu information if it's available
$menu = \JFactory::getApplication()->getMenu();
if (is_object($menu) && $item = $menu->getActive())
{
$params = $menu->getParams($item->id);
// Set default state data
$model->setState('parameters.menu', $params);
}
}
return $model;
}
/**
* Method to get the controller name
*
* The dispatcher name is set by default parsed using the classname, or it can be set
* by passing a $config['name'] in the class constructor
*
* @return string The name of the dispatcher
*
* @since 3.0
* @throws \Exception
*/
public function getName()
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/(.*)Controller/i', get_class($this), $r))
{
throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
}
$this->name = strtolower($r[1]);
}
return $this->name;
}
/**
* Get the last task that is being performed or was most recently performed.
*
* @return string The task that is being performed or was most recently performed.
*
* @since 3.0
*/
public function getTask()
{
return $this->task;
}
/**
* Gets the available tasks in the controller.
*
* @return array Array[i] of task names.
*
* @since 3.0
*/
public function getTasks()
{
return $this->methods;
}
/**
* Method to get a reference to the current view and load it if necessary.
*
* @param string $name The view name. Optional, defaults to the controller name.
* @param string $type The view type. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for view. Optional.
*
* @return \JViewLegacy Reference to the view or an error.
*
* @since 3.0
* @throws \Exception
*/
public function getView($name = '', $type = '', $prefix = '', $config = array())
{
// @note We use self so we only access stuff in this class rather than in all classes.
if (!isset(self::$views))
{
self::$views = array();
}
if (empty($name))
{
$name = $this->getName();
}
if (empty($prefix))
{
$prefix = $this->getName() . 'View';
}
if (empty(self::$views[$name][$type][$prefix]))
{
if ($view = $this->createView($name, $prefix, $type, $config))
{
self::$views[$name][$type][$prefix] = & $view;
}
else
{
throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_NOT_FOUND', $name, $type, $prefix), 404);
}
}
return self::$views[$name][$type][$prefix];
}
/**
* Method to add a record ID to the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit list.
*
* @return void
*
* @since 3.0
*/
protected function holdEditId($context, $id)
{
$app = \JFactory::getApplication();
$values = (array) $app->getUserState($context . '.id');
// Add the id to the list if non-zero.
if (!empty($id))
{
$values[] = (int) $id;
$values = array_unique($values);
$app->setUserState($context . '.id', $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Holding edit ID %s.%s %s',
$context,
$id,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
}
}
/**
* Redirects the browser or returns false if no redirect is set.
*
* @return boolean False if no redirect exists.
*
* @since 3.0
*/
public function redirect()
{
if ($this->redirect)
{
$app = \JFactory::getApplication();
// Enqueue the redirect message
$app->enqueueMessage($this->message, $this->messageType);
// Execute the redirect
$app->redirect($this->redirect);
}
return false;
}
/**
* Register the default task to perform if a mapping is not found.
*
* @param string $method The name of the method in the derived class to perform if a named task is not found.
*
* @return \JControllerLegacy A \JControllerLegacy object to support chaining.
*
* @since 3.0
*/
public function registerDefaultTask($method)
{
$this->registerTask('__default', $method);
return $this;
}
/**
* Register (map) a task to a method in the class.
*
* @param string $task The task.
* @param string $method The name of the method in the derived class to perform for this task.
*
* @return \JControllerLegacy A \JControllerLegacy object to support chaining.
*
* @since 3.0
*/
public function registerTask($task, $method)
{
if (in_array(strtolower($method), $this->methods))
{
$this->taskMap[strtolower($task)] = $method;
}
return $this;
}
/**
* Unregister (unmap) a task in the class.
*
* @param string $task The task.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function unregisterTask($task)
{
unset($this->taskMap[strtolower($task)]);
return $this;
}
/**
* Method to check whether an ID is in the edit list.
*
* @param string $context The context for the session storage.
* @param integer $id The ID of the record to add to the edit list.
*
* @return void
*
* @since 3.0
*/
protected function releaseEditId($context, $id)
{
$app = \JFactory::getApplication();
$values = (array) $app->getUserState($context . '.id');
// Do a strict search of the edit list values.
$index = array_search((int) $id, $values, true);
if (is_int($index))
{
unset($values[$index]);
$app->setUserState($context . '.id', $values);
if (defined('JDEBUG') && JDEBUG)
{
\JLog::add(
sprintf(
'Releasing edit ID %s.%s %s',
$context,
$id,
str_replace("\n", ' ', print_r($values, 1))
),
\JLog::INFO,
'controller'
);
}
}
}
/**
* Sets the internal message that is passed with a redirect
*
* @param string $text Message to display on redirect.
* @param string $type Message type. Optional, defaults to 'message'.
*
* @return string Previous message
*
* @since 3.0
*/
public function setMessage($text, $type = 'message')
{
$previous = $this->message;
$this->message = $text;
$this->messageType = $type;
return $previous;
}
/**
* Sets an entire array of search paths for resources.
*
* @param string $type The type of path to set, typically 'view' or 'model'.
* @param string $path The new set of search paths. If null or false, resets to the current directory only.
*
* @return void
*
* @since 3.0
*/
protected function setPath($type, $path)
{
// Clear out the prior search dirs
$this->paths[$type] = array();
// Actually add the user-specified directories
$this->addPath($type, $path);
}
/**
* Checks for a form token in the request.
*
* Use in conjunction with \JHtml::_('form.token') or \JSession::getFormToken.
*
* @param string $method The request method in which to look for the token key.
* @param boolean $redirect Whether to implicitly redirect user to the referrer page on failure or simply return false.
*
* @return boolean True if found and valid, otherwise return false or redirect to referrer page.
*
* @since 3.7.0
* @see \JSession::checkToken()
*/
public function checkToken($method = 'post', $redirect = true)
{
$valid = \JSession::checkToken($method);
if (!$valid && $redirect)
{
$referrer = $this->input->server->getString('HTTP_REFERER');
if (!\JUri::isInternal($referrer))
{
$referrer = 'index.php';
}
$app = \JFactory::getApplication();
$app->enqueueMessage(\JText::_('JINVALID_TOKEN_NOTICE'), 'warning');
$app->redirect($referrer);
}
return $valid;
}
/**
* Set a URL for browser redirection.
*
* @param string $url URL to redirect to.
* @param string $msg Message to display on redirect. Optional, defaults to value set internally by controller, if any.
* @param string $type Message type. Optional, defaults to 'message' or the type set by a previous call to setMessage.
*
* @return \JControllerLegacy This object to support chaining.
*
* @since 3.0
*/
public function setRedirect($url, $msg = null, $type = null)
{
$this->redirect = $url;
if ($msg !== null)
{
// Controller may have set this directly
$this->message = $msg;
}
// Ensure the type is not overwritten by a previous call to setMessage.
if (empty($type))
{
if (empty($this->messageType))
{
$this->messageType = 'message';
}
}
// If the type is explicitly set, set it.
else
{
$this->messageType = $type;
}
return $this;
}
}
AdminController.php 0000604 00000022603 15245611237 0010356 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\MVC\Controller;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\Utilities\ArrayHelper;
/**
* Base class for a Joomla Administrator Controller
*
* Controller (controllers are where you put all the actual code) Provides basic
* functionality, such as rendering views (aka displaying templates).
*
* @since 1.6
*/
class AdminController extends BaseController
{
/**
* The URL option for the component.
*
* @var string
* @since 1.6
*/
protected $option;
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.6
*/
protected $text_prefix;
/**
* The URL view list variable.
*
* @var string
* @since 1.6
*/
protected $view_list;
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param MVCFactoryInterface $factory The factory.
*
* @see \JControllerLegacy
* @since 1.6
* @throws \Exception
*/
public function __construct($config = array(), MVCFactoryInterface $factory = null)
{
parent::__construct($config, $factory);
// Define standard task mappings.
// Value = 0
$this->registerTask('unpublish', 'publish');
// Value = 2
$this->registerTask('archive', 'publish');
// Value = -2
$this->registerTask('trash', 'publish');
// Value = -3
$this->registerTask('report', 'publish');
$this->registerTask('orderup', 'reorder');
$this->registerTask('orderdown', 'reorder');
// Guess the option as com_NameOfController.
if (empty($this->option))
{
$this->option = 'com_' . strtolower($this->getName());
}
// Guess the \JText message prefix. Defaults to the option.
if (empty($this->text_prefix))
{
$this->text_prefix = strtoupper($this->option);
}
// Guess the list view as the suffix, eg: OptionControllerSuffix.
if (empty($this->view_list))
{
$r = null;
if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r))
{
throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
}
$this->view_list = strtolower($r[2]);
}
}
/**
* Removes an item.
*
* @return void
*
* @since 1.6
*/
public function delete()
{
// Check for request forgeries
$this->checkToken();
// Get items to remove from the request.
$cid = (array) $this->input->get('cid', array(), 'int');
// Remove zero values resulting from input filter
$cid = array_filter($cid);
if (empty($cid))
{
\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
}
else
{
// Get the model.
$model = $this->getModel();
// Remove the items.
if ($model->delete($cid))
{
$this->setMessage(\JText::plural($this->text_prefix . '_N_ITEMS_DELETED', count($cid)));
}
else
{
$this->setMessage($model->getError(), 'error');
}
// Invoke the postDelete method to allow for the child class to access the model.
$this->postDeleteHook($model, $cid);
}
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
}
/**
* Function that allows child controller access to model data
* after the item has been deleted.
*
* @param \JModelLegacy $model The data model object.
* @param integer $id The validated data.
*
* @return void
*
* @since 3.1
*/
protected function postDeleteHook(\JModelLegacy $model, $id = null)
{
}
/**
* Method to publish a list of items
*
* @return void
*
* @since 1.6
*/
public function publish()
{
// Check for request forgeries
$this->checkToken();
// Get items to publish from the request.
$cid = (array) $this->input->get('cid', array(), 'int');
$data = array('publish' => 1, 'unpublish' => 0, 'archive' => 2, 'trash' => -2, 'report' => -3);
$task = $this->getTask();
$value = ArrayHelper::getValue($data, $task, 0, 'int');
// Remove zero values resulting from input filter
$cid = array_filter($cid);
if (empty($cid))
{
\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
}
else
{
// Get the model.
$model = $this->getModel();
// Publish the items.
try
{
$model->publish($cid, $value);
$errors = $model->getErrors();
$ntext = null;
if ($value === 1)
{
if ($errors)
{
\JFactory::getApplication()->enqueueMessage(\JText::plural($this->text_prefix . '_N_ITEMS_FAILED_PUBLISHING', count($cid)), 'error');
}
else
{
$ntext = $this->text_prefix . '_N_ITEMS_PUBLISHED';
}
}
elseif ($value === 0)
{
$ntext = $this->text_prefix . '_N_ITEMS_UNPUBLISHED';
}
elseif ($value === 2)
{
$ntext = $this->text_prefix . '_N_ITEMS_ARCHIVED';
}
else
{
$ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
}
if ($ntext !== null)
{
$this->setMessage(\JText::plural($ntext, count($cid)));
}
}
catch (\Exception $e)
{
$this->setMessage($e->getMessage(), 'error');
}
}
$extension = $this->input->get('extension');
$extensionURL = $extension ? '&extension=' . $extension : '';
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $extensionURL, false));
}
/**
* Changes the order of one or more records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function reorder()
{
// Check for request forgeries.
$this->checkToken();
$ids = (array) $this->input->post->get('cid', array(), 'int');
$inc = $this->getTask() === 'orderup' ? -1 : 1;
// Remove zero values resulting from input filter
$ids = array_filter($ids);
$model = $this->getModel();
$return = $model->reorder($ids, $inc);
if ($return === false)
{
// Reorder failed.
$message = \JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED', $model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');
return false;
}
else
{
// Reorder succeeded.
$message = \JText::_('JLIB_APPLICATION_SUCCESS_ITEM_REORDERED');
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message);
return true;
}
}
/**
* Method to save the submitted ordering values for records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function saveorder()
{
// Check for request forgeries.
$this->checkToken();
// Get the input
$pks = (array) $this->input->post->get('cid', array(), 'int');
$order = (array) $this->input->post->get('order', array(), 'int');
// Remove zero PK's and corresponding order values resulting from input filter for PK
foreach ($pks as $i => $pk)
{
if ($pk === 0)
{
unset($pks[$i]);
unset($order[$i]);
}
}
// Get the model
$model = $this->getModel();
// Save the ordering
$return = $model->saveorder($pks, $order);
if ($return === false)
{
// Reorder failed
$message = \JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED', $model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');
return false;
}
else
{
// Reorder succeeded.
$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_ORDERING_SAVED'));
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
return true;
}
}
/**
* Check in of one or more records.
*
* @return boolean True on success
*
* @since 1.6
*/
public function checkin()
{
// Check for request forgeries.
$this->checkToken();
$ids = (array) $this->input->post->get('cid', array(), 'int');
// Remove zero values resulting from input filter
$ids = array_filter($ids);
$model = $this->getModel();
$return = $model->checkin($ids);
if ($return === false)
{
// Checkin failed.
$message = \JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError());
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');
return false;
}
else
{
// Checkin succeeded.
$message = \JText::plural($this->text_prefix . '_N_ITEMS_CHECKED_IN', count($ids));
$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message);
return true;
}
}
/**
* Method to save the submitted ordering values for records via AJAX.
*
* @return void
*
* @since 3.0
*/
public function saveOrderAjax()
{
// Check for request forgeries.
$this->checkToken();
// Get the input
$pks = (array) $this->input->post->get('cid', array(), 'int');
$order = (array) $this->input->post->get('order', array(), 'int');
// Remove zero PK's and corresponding order values resulting from input filter for PK
foreach ($pks as $i => $pk)
{
if ($pk === 0)
{
unset($pks[$i]);
unset($order[$i]);
}
}
// Get the model
$model = $this->getModel();
// Save the ordering
$return = $model->saveorder($pks, $order);
if ($return)
{
echo '1';
}
// Close the application
\JFactory::getApplication()->close();
}
}
Exception/NotADataView.php 0000604 00000000633 15245611465 0011510 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller\Exception;
defined('_JEXEC') || die;
use InvalidArgumentException;
/**
* Exception thrown when the provided View does not implement DataViewInterface
*/
class NotADataView extends InvalidArgumentException
{
}
ConfigurationWizard.php 0000604 00000002717 15245617260 0011260 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Component\ComponentHelper;
/**
* Controller for the configuration wizard
*/
class ConfigurationWizard extends Controller
{
use CustomACL;
use PredefinedTaskList;
/** @var bool */
private $noFlush = false;
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['main', 'ajax']);
$this->noFlush = ComponentHelper::getParams('com_akeeba')->get('no_flush', 0) == 1;
}
/**
* Handles AJAX request by proxying the call to the Model, which does all the work, and returning the JSON encoded
* result back to the browser.
*/
public function ajax()
{
/** @var \Akeeba\Backup\Admin\Model\ConfigurationWizard $model */
$model = $this->getModel();
$model->setState('act', $this->input->get('act', '', 'cmd'));
$ret = $model->runAjax();
@ob_end_clean();
echo '###' . json_encode($ret) . '###';
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
}
.htaccess 0000604 00000000246 15245617260 0006350 0 ustar 00 <IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
<RequireAll>
Require all denied
</RequireAll>
</IfModule>
DatabaseFilters.php 0000604 00000003607 15245617260 0010324 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
/**
* Database Filters controller
*/
class DatabaseFilters extends Controller
{
use CustomACL;
use PredefinedTaskList;
/** @var bool */
private $noFlush = false;
/**
* Should I decode the "action" JSON data as an associative array? Default is false (meaning we're decoding as an
* stdClass object).
*
* @var bool
*/
protected $decodeJsonAsArray = false;
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['main', 'ajax']);
}
/**
* Handles the "main" task, which displays a folder and file list
*
*/
public function main()
{
$task = $this->input->get('task', 'normal', 'cmd');
/** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */
$model = $this->getModel();
$model->setState('browse_task', $task);
$this->display(false);
}
/**
* AJAX proxy
*/
public function ajax()
{
// Parse the JSON data and reset the action query param to the resulting array
$action_json = $this->input->get('action', '', 'none', 2);
$action = json_decode($action_json, $this->decodeJsonAsArray);
/** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */
$model = $this->getModel();
$model->setState('action', $action);
$ret = $model->doAjax();
@ob_end_clean();
echo '###' . json_encode($ret) . '###';
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
}
ControlPanel.php 0000604 00000021523 15245617260 0007664 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Helper\Utils;
use Akeeba\Backup\Admin\Model\Backup as BackupModel;
use Akeeba\Backup\Admin\Model\ConfigurationWizard;
use Akeeba\Backup\Admin\Model\Updates;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Factory\Exception\ModelNotFound;
use FOF40\Utils\ViewManifestMigration;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use RuntimeException;
/**
* The Control Panel controller class
*/
class ControlPanel extends Controller
{
use CustomACL, PredefinedTaskList;
public function __construct(Container $container, array $config = [])
{
parent::__construct($container, $config);
$this->setPredefinedTaskList([
'main', 'SwitchProfile', 'applydlid', 'resetSecretWord',
'forceUpdateDb', 'dismissUpsell', 'fixOutputDirectory', 'checkOutputDirectory', 'addRandomToFilename',
]);
}
public function SwitchProfile()
{
// CSRF prevention
$this->csrfProtection();
$newProfile = $this->input->get('profileid', -10, 'int');
if (!is_numeric($newProfile) || ($newProfile <= 0))
{
$this->setRedirect(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba', \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_PROFILE_SWITCH_ERROR'), 'error');
return;
}
$this->container->platform->setSessionVar('profile', $newProfile, 'akeeba');
$returnurl = $this->input->get('returnurl', '', 'base64');
$url = Utils::safeDecodeReturnUrl($returnurl);
if (empty($url))
{
$url = \Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba';
}
$this->setRedirect($url, \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_PROFILE_SWITCH_OK'));
}
/**
* Applies the Download ID when the user is prompted about it in the Control Panel
*/
public function applydlid()
{
// CSRF prevention
$this->csrfProtection();
$msg = \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_ERR_INVALIDDOWNLOADID');
$msgType = 'error';
$dlid = $this->input->getString('dlid', '');
/** @var Updates $updateModel */
$updateModel = $this->container->factory->model('Updates')->tmpInstance();
$dlid = $updateModel->sanitizeLicenseKey($dlid);
$isValidDLID = $updateModel->isValidLicenseKey($dlid);
// If the Download ID seems legit let's apply it
if ($isValidDLID)
{
$msg = null;
$msgType = null;
$updateModel->setLicenseKey($dlid);
}
// Redirect back to the control panel
$returnurl = $this->input->get('returnurl', '', 'base64');
$url = Utils::safeDecodeReturnUrl($returnurl);
if (empty($url))
{
$url = \Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba';
}
$this->setRedirect($url, $msg, $msgType);
}
/**
* Reset the Secret Word for front-end and remote backup
*
* @return void
*/
public function resetSecretWord()
{
// CSRF prevention
$this->csrfProtection();
$newSecret = $this->container->platform->getSessionVar('newSecretWord', null, 'akeeba.cpanel');
if (empty($newSecret))
{
$random = new \Akeeba\Engine\Util\RandomValue();
$newSecret = $random->generateString(32);
$this->container->platform->setSessionVar('newSecretWord', $newSecret, 'akeeba.cpanel');
}
$this->container->params->set('frontend_secret_word', $newSecret);
$this->container->params->save();
$this->container->platform->setSessionVar('newSecretWord', null, 'akeeba.cpanel');
$msg = \Joomla\CMS\Language\Text::sprintf('COM_AKEEBA_CPANEL_MSG_FESECRETWORD_RESET', $newSecret);
$url = 'index.php?option=com_akeeba';
$this->setRedirect($url, $msg);
}
/**
* Resets the "updatedb" flag and forces the database updates
*/
public function forceUpdateDb()
{
// Reset the flag so the updates could take place
$this->container->params->set('updatedb', null);
$this->container->params->save();
/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
$model = $this->getModel();
try
{
$model->checkAndFixDatabase();
}
catch (\RuntimeException $e)
{
// This should never happen, since we reset the flag before execute the update, but you never know
}
$this->setRedirect('index.php?option=com_akeeba');
}
/**
* Dismisses the Core to Pro upsell for 15 days
*
* @return void
*/
public function dismissUpsell()
{
// Reset the flag so the updates could take place
$this->container->params->set('lastUpsellDismiss', time());
$this->container->params->save();
$this->setRedirect('index.php?option=com_akeeba');
}
/**
* Check the security of the backup output directory and return the results for consumption through AJAX
*
* @return void
*
* @throws Exception
*
* @since 7.0.3
*/
public function checkOutputDirectory()
{
/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
$model = $this->getModel();
$outDir = $model->getOutputDirectory();
try
{
$result = $model->getOutputDirectoryWebAccessibleState($outDir);
}
catch (RuntimeException $e)
{
$result = [
'readFile' => false,
'listFolder' => false,
'isSystem' => $model->isOutputDirectoryInSystemFolder(),
'hasRandom' => $model->backupFilenameHasRandom(),
];
}
@ob_end_clean();
echo '###' . json_encode($result) . '###';
$this->container->platform->closeApplication();
}
/**
* Add security files to the output directory of the currently configured backup profile
*
* @return void
*
* @throws Exception
*
* @since 7.0.3
*/
public function fixOutputDirectory()
{
// CSRF prevention
$this->csrfProtection();
/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
$model = $this->getModel();
$outDir = $model->getOutputDirectory();
$fsUtils = Factory::getFilesystemTools();
$fsUtils->ensureNoAccess($outDir, true);
$this->setRedirect('index.php?option=com_akeeba');
}
/**
* Adds the [RANDOM] variable to the backup output filename, save the configuration and reload the Control Panel.
*
* @return void
*
* @throws Exception
*
* @since 7.0.3
*/
public function addRandomToFilename()
{
// CSRF prevention
$this->csrfProtection();
$registry = Factory::getConfiguration();
$templateName = $registry->get('akeeba.basic.archive_name');
if (strpos($templateName, '[RANDOM]') === false)
{
$templateName .= '-[RANDOM]';
$registry->set('akeeba.basic.archive_name', $templateName);
Platform::getInstance()->save_configuration();
}
$this->setRedirect('index.php?option=com_akeeba');
}
/**
* Run everything necessary to display the Control Panel page
*
* @return void
*
* @throws Exception
*
* @since 5.0.0
*/
protected function onBeforeMain()
{
/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
$model = $this->getModel();
$engineConfig = Factory::getConfiguration();
// Invalidate stale backups
$params = $this->container->params;
try
{
Factory::resetState([
'global' => true,
'log' => false,
'maxrun' => $params->get('failure_timeout', 180),
]);
}
catch (Exception $e)
{
// This will die if the output directory is invalid. Let it die, then.
}
// Just in case the reset() loaded a stale configuration...
Platform::getInstance()->load_configuration();
Platform::getInstance()->apply_quirk_definitions();
// Let's make sure the temporary and output directories are set correctly and writable...
/** @var ConfigurationWizard $wizmodel */
$wizmodel = $this->container->factory->model('ConfigurationWizard')->tmpInstance();
$wizmodel->autofixDirectories();
// Rebase Off-site Folder Inclusion filters to use site path variables
/** @var \Akeeba\Backup\Admin\Model\IncludeFolders $incFoldersModel */
try
{
$incFoldersModel = $this->container->factory->model('IncludeFolders')->tmpInstance();
$incFoldersModel->rebaseFiltersToSiteDirs();
}
catch (ModelNotFound $e)
{
// Not a problem. This is expected to happen in the Core version.
}
// Check if we need to toggle the settings encryption feature
$model->checkSettingsEncryption();
// Convert existing log files to the new .log.php format
/** @var BackupModel $backupModel */
$backupModel = $this->container->factory->model('Backup')->tmpInstance();
$backupModel->convertLogFiles();
// Run the automatic update site refresh
/** @var Updates $updateModel */
$updateModel = $this->container->factory->model('Updates')->tmpInstance();
$updateModel->refreshUpdateSite();
ViewManifestMigration::migrateJoomla4MenuXMLFiles($this->container);
ViewManifestMigration::removeJoomla3LegacyViews($this->container);
}
}
Profiles.php 0000604 00000006223 15245617260 0007047 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\DataController;
use Joomla\CMS\Language\Text;
use RuntimeException;
class Profiles extends DataController
{
use CustomACL;
/**
* Imports an exported profile .json file
*/
public function import()
{
$this->csrfProtection();
if (!$this->container->platform->authorise('akeeba.configure', 'com_akeeba'))
{
throw new RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
/** @var \Akeeba\Backup\Admin\Model\Profiles $model */
$model = $this->getModel();
// Get some data from the request
$file = $this->input->files->get('importfile', array(), 'array');
if (!isset($file['name']))
{
$this->setRedirect('index.php?option=com_akeeba&view=Profiles', \Joomla\CMS\Language\Text::_('MSG_UPLOAD_INVALID_REQUEST'), 'error');
return;
}
// Load the file data
$data = @file_get_contents($file['tmp_name']);
@unlink($file['tmp_name']);
// JSON decode
$data = json_decode($data, true);
// Import
$message = \Joomla\CMS\Language\Text::_('COM_AKEEBA_PROFILES_MSG_IMPORT_COMPLETE');
$messageType = null;
try
{
$model->reset()->import($data);
}
catch (RuntimeException $e)
{
$message = $e->getMessage();
$messageType = 'error';
}
// Redirect back to the main page
$this->setRedirect('index.php?option=com_akeeba&view=Profiles', $message, $messageType);
}
/**
* Enable the Quick Icon for a record
*
* @since 6.1.2
* @throws \Exception
*/
public function quickicon_publish()
{
$this->setQuickIcon(1);
}
/**
* Disable the Quick Icon for a record
*
* @since 6.1.2
* @throws \Exception
*/
public function quickicon_unpublish()
{
$this->setQuickIcon(0);
}
/**
* Sets the Quick Icon status for the record.
*
* @param int|bool $published Should this profile have a Quick Icon?
*
* @return void
* @throws \Exception
*
* @since 6.1.2
*/
private function setQuickIcon($published)
{
// CSRF prevention
$this->csrfProtection();
/** @var \Akeeba\Backup\Admin\Model\Profiles $model */
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = false;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->save([
'quickicon' => $published ? 1 : 0
]);
}
}
catch (\Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if ($customURL = $this->input->getBase64('returnurl', ''))
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
}
Log.php 0000604 00000006414 15245617260 0006007 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Model\Log as LogModel;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Controller\Controller;
class Log extends Controller
{
use CustomACL {
CustomACL::onBeforeExecute as onCustomACLBeforeExecute;
}
/** @var bool */
private $noFlush = false;
protected function onBeforeExecute(&$task)
{
$this->onCustomACLBeforeExecute($task);
$profile_id = $this->input->getInt('profileid', null);
if (!empty($profile_id) && is_numeric($profile_id) && ($profile_id > 0))
{
$this->container->platform->setSessionVar('profile', $profile_id, 'akeeba');
}
}
/**
* Display the log page
*
* @return void
*/
public function onBeforeDefault()
{
$tag = $this->input->get('tag', null, 'cmd');
$latest = $this->input->get('latest', false, 'int');
if (empty($tag))
{
$tag = null;
}
/** @var LogModel $model */
$model = $this->getModel();
if ($latest)
{
$logFiles = $model->getLogFiles();
$tag = array_shift($logFiles);
}
$model->setState('tag', $tag);
Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());
}
/**
* Renders the contents of the log, used inside the IFRAME of the log page
*
* @return void
*/
public function iframe()
{
$tag = $this->input->get('tag', null, 'cmd');
if (empty($tag))
{
$tag = null;
}
/** @var LogModel $model */
$model = $this->getModel();
$model->setState('tag', $tag);
Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());
$this->display();
}
/**
* Download the log file as a text file
*
* @return void
*/
public function download()
{
Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());
$tag = $this->input->get('tag', null, 'cmd');
if (empty($tag))
{
$tag = null;
}
$asAttachment = $this->input->getBool('attachment', true);
@ob_end_clean(); // In case some braindead plugin spits its own HTML
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header("Content-Description: File Transfer");
header('Content-Type: text/plain');
if ($asAttachment)
{
header('Content-Disposition: attachment; filename="Akeeba Backup Debug Log.txt"');
}
/** @var LogModel $model */
$model = $this->getModel();
$model->setState('tag', $tag);
$model->echoRawLog();
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
public function inlineRaw()
{
Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());
$tag = $this->input->get('tag', null, 'cmd');
if (empty($tag))
{
$tag = null;
}
/** @var LogModel $model */
$model = $this->getModel();
$model->setState('tag', $tag);
echo "<pre>";
$model->echoRawLog();
echo "</pre>";
}
}
FTPBrowser.php 0000604 00000002610 15245617260 0007255 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\Controller;
/**
* Controller for the FTP folder browser
*/
class FTPBrowser extends Controller
{
use CustomACL;
/** @var bool */
private $noFlush = false;
protected function onBeforeMain()
{
/** @var \Akeeba\Backup\Admin\Model\FTPBrowser $model */
$model = $this->getModel();
// Grab the data and push them to the model
$model->host = $this->input->get('host', '', 'string');
$model->port = $this->input->get('port', 21, 'int');
$model->passive = $this->input->get('passive', 1, 'int');
$model->ssl = $this->input->get('ssl', 0, 'int');
$model->username = $this->input->get('username', '', 'none', 2);
$model->password = $this->input->get('password', '', 'none', 2);
$model->directory = $this->input->get('directory', '', 'none', 2);
if (empty($model->port))
{
$model->port = $model->ssl ? 990 : 21;
}
$ret = $model->doBrowse();
@ob_end_clean();
echo '###' . json_encode($ret) . '###';
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
}
Mixin/CustomACL.php 0000604 00000003760 15245617260 0010145 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller\Mixin;
// Protect from unauthorized access
defined('_JEXEC') || die();
use RuntimeException;
use Joomla\CMS\Language\Text;
trait CustomACL
{
protected function onBeforeExecute(&$task)
{
$this->akeebaBackupACLCheck($this->view, $this->task);
}
/**
* Checks if the currently logged in user has the required ACL privileges to access the current view. If not, a
* RuntimeException is thrown.
*
* @return void
*/
protected function akeebaBackupACLCheck($view, $task)
{
// Akeeba Backup-specific ACL checks. All views not listed here are limited by the akeeba.configure privilege.
$viewACLMap = [
'ControlPanel' => 'core.manage',
'Backup' => 'akeeba.backup',
'Manage' => 'core.manage',
'Manage.download' => 'akeeba.download',
'Manage.remove' => 'akeeba.download',
'Manage.deletefiles' => 'akeeba.download',
'Manage.showcomment' => 'akeeba.backup',
'Manage.save' => 'akeeba.download',
'Manage.restore' => 'akeeba.configure',
'Manage.cancel' => 'akeeba.backup',
'Upload' => 'akeeba.backup',
'RemoteFiles' => 'akeeba.download',
'Transfer' => 'akeeba.download',
];
// Default
$privilege = 'akeeba.configure';
// Just the view was found
if (array_key_exists($view, $viewACLMap))
{
$privilege = $viewACLMap[$view];
}
// The view AND task was found
if (array_key_exists($view . '.' . $task, $viewACLMap))
{
$privilege = $viewACLMap[$view . '.' . $task];
}
// If an empty privilege is defined do not perform any ACL checks
if (empty($privilege))
{
return;
}
if (!$this->container->platform->authorise($privilege, 'com_akeeba'))
{
throw new RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
}
}
Backup.php 0000604 00000014066 15245617260 0006475 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Helper\Utils;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
/**
* Backup page controller
*/
class Backup extends Controller
{
use CustomACL;
use PredefinedTaskList;
/** @var bool */
private $noFlush = false;
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList([
'main',
'ajax',
]);
$this->noFlush = ComponentHelper::getParams('com_akeeba')->get('no_flush', 0) == 1;
}
/**
* This task handles the AJAX requests
*/
public function ajax()
{
/** @var \Akeeba\Backup\Admin\Model\Backup $model */
$model = $this->getModel();
// Push all necessary information to the model's state
$model->setState('profile', $this->input->get('profileid', Platform::getInstance()->get_active_profile(), 'int'));
$model->setState('ajax', $this->input->get('ajax', '', 'cmd'));
$model->setState('description', $this->input->get('description', '', 'string'));
$model->setState('comment', $this->input->get('comment', '', 'html', 2));
$model->setState('jpskey', $this->input->get('jpskey', '', 'raw', 2));
$model->setState('angiekey', $this->input->get('angiekey', '', 'raw', 2));
$model->setState('backupid', $this->input->get('backupid', null, 'cmd'));
$model->setState('tag', $this->input->get('tag', 'backend', 'cmd'));
$model->setState('errorMessage', $this->input->getString('errorMessage', ''));
// System Restore Point backup state variables (obsolete)
$model->setState('type', strtolower($this->input->get('type', '', 'cmd')));
$model->setState('name', strtolower($this->input->get('name', '', 'cmd')));
$model->setState('group', strtolower($this->input->get('group', '', 'cmd')));
$model->setState('customdirs', $this->input->get('customdirs', [], 'array', 2));
$model->setState('customfiles', $this->input->get('customfiles', [], 'array', 2));
$model->setState('extraprefixes', $this->input->get('extraprefixes', [], 'array', 2));
$model->setState('customtables', $this->input->get('customtables', [], 'array', 2));
$model->setState('skiptables', $this->input->get('skiptables', [], 'array', 2));
$model->setState('langfiles', $this->input->get('langfiles', [], 'array', 2));
$model->setState('xmlname', $this->input->getString('xmlname', ''));
// Set up the tag
define('AKEEBA_BACKUP_ORIGIN', $this->input->get('tag', 'backend', 'cmd'));
// Run the backup step
$ret_array = $model->runBackup();
// We use this nasty trick to avoid broken 3PD plugins from barfing all over our output
@ob_end_clean();
header('Content-type: text/plain');
header('Connection: close');
echo '###' . json_encode($ret_array) . '###';
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
/**
* Default task; shows the initial page where the user selects a profile and enters description and comment
*/
protected function onBeforeMain()
{
// Did the user ask to switch the active profile?
$newProfile = $this->input->get('profileid', -10, 'int');
$autostart = $this->input->get('autostart', 0, 'int');
if (is_numeric($newProfile) && ($newProfile > 0))
{
/**
* We have to remove CSRF protection due to the way the Joomla administrator menu manager works. Menu item
* options are passed as URL parameters. However, we cannot pass dynamic parameters (like the token). This
* means that a user can create a menu item with a specific backup profile ID. Normally this would cause a
* 403 which is frustrating to the user because they might want to give their client the option to run a
* backup with a specific profile AND let them enter a description and comment. Therefore we have to remove
* the CSRF protection.
*
* NB! We do understand the potential risk involved. Between Joomla's AMATEURISH implementation of custom
* administrator menus and user demands for features we have to (have these very vocal users and everyone
* else) assume that (actually really small) risk.
*/
// $this->csrfProtection();
$this->container->platform->setSessionVar('profile', $newProfile, 'akeeba');
/**
* DO NOT REMOVE!
*
* The Model will only try to load the configuration after nuking the factory. This causes Profile 1 to be
* loaded first. Then it figures out it needs to load a different profile and it does – but the protected keys
* are NOT replaced, meaning that certain configuration parameters are not replaced. Most notably, the chain.
* This causes backups to behave weirdly. So, DON'T REMOVE THIS UNLESS WE REFACTOR THE MODEL.
*/
Platform::getInstance()->load_configuration($newProfile);
}
// Deactivate the menus
Factory::getApplication()->input->set('hidemainmenu', 1);
/** @var \Akeeba\Backup\Admin\Model\Backup $model */
$model = $this->getModel();
// Sanitize the return URL
$returnUrl = $this->input->get('returnurl', '', 'raw');
$returnUrl = Utils::safeDecodeReturnUrl($returnUrl);
// Push data to the model
$model->setState('profile', $this->input->get('profileid', -10, 'int'));
$model->setState('description', $this->input->get('description', '', 'string', 2));
$model->setState('comment', $this->input->get('comment', '', 'html', 2));
$model->setState('ajax', $this->input->get('ajax', '', 'cmd'));
$model->setState('autostart', $autostart);
$model->setState('jpskey', $this->input->get('jpskey', '', 'raw', 2));
$model->setState('angiekey', $this->input->get('angiekey', '', 'raw', 2));
$model->setState('returnurl', $returnUrl);
$model->setState('backupid', $this->input->get('backupid', null, 'cmd'));
}
}
Profile.php 0000604 00000000503 15245617260 0006657 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
class Profile extends Profiles
{
}
SFTPBrowser.php 0000604 00000002604 15245617260 0007403 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\Controller;
/**
* Controller for the SFTP folder browser
*/
class SFTPBrowser extends Controller
{
use CustomACL;
/** @var bool */
private $noFlush = false;
protected function onBeforeMain()
{
/** @var \Akeeba\Backup\Admin\Model\SFTPBrowser $model */
$model = $this->getModel();
// Grab the data and push them to the model
$model->host = $this->input->get('host', '', 'string');
$model->port = $this->input->get('port', 21, 'int');
$model->username = $this->input->get('username', '', 'none', 2);
$model->password = $this->input->get('password', '', 'none', 2);
$model->privkey = $this->input->get('privkey', '', 'none', 2);
$model->pubkey = $this->input->get('pubkey', '', 'none', 2);
$model->directory = $this->input->get('directory', '', 'none', 2);
if (empty($model->port))
{
$model->port = 22;
}
$ret = $model->doBrowse();
@ob_end_clean();
echo '###' . json_encode($ret) . '###';
if (!$this->noFlush)
{
flush();
}
$this->container->platform->closeApplication();
}
}
Manage.php 0000604 00000022651 15245617260 0006457 0 ustar 00 <?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Backup\Admin\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Model\Statistics;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
/**
* Backup page controller
*/
class Manage extends Controller
{
use CustomACL;
/** @var bool */
private $noFlush = false;
public function __construct(Container $container, array $config)
{
if (!is_array($config))
{
$config = [];
}
$config['modelName'] = 'Statistics';
parent::__construct($container, $config);
}
/**
* Downloads the backup archive of the specified backup record
*
* @return void
*/
public function download()
{
$ids = $this->getIDsFromRequest();
$id = count($ids) ? array_pop($ids) : -1;
$part = $this->input->get('part', -1, 'int');
if ($id <= 0)
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');
return;
}
$stat = Platform::getInstance()->get_statistics($id);
$allFilenames = Factory::getStatistics()->get_all_filenames($stat);
$filename = null;
// Check single part files
$countAllFilenames = $allFilenames === null ? 0 : count($allFilenames);
if (($countAllFilenames == 1) && ($part == -1))
{
$filename = array_shift($allFilenames);
}
elseif (($countAllFilenames > 0) && ($countAllFilenames > $part) && ($part >= 0))
{
$filename = $allFilenames[ $part ];
}
if (is_null($filename) || empty($filename) || !@file_exists($filename))
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDDOWNLOAD'), 'error');
return;
}
// Remove php's time limit
if (function_exists('ini_get') && function_exists('set_time_limit'))
{
if (!ini_get('safe_mode'))
{
@set_time_limit(0);
}
}
$basename = @basename($filename);
$filesize = @filesize($filename);
$extension = strtolower(str_replace(".", "", strrchr($filename, ".")));
while (@ob_end_clean())
{
;
}
@clearstatcache();
// Send MIME headers
header('MIME-Version: 1.0');
header('Content-Disposition: attachment; filename="' . $basename . '"');
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
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;
}
// Notify of filesize, if this info is available
if ($filesize > 0)
{
header('Content-Length: ' . @filesize($filename));
}
// Disable caching
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Expires: 0");
header('Pragma: no-cache');
if (!$this->noFlush)
{
flush();
}
if (!$filesize)
{
// If the filesize is not reported, hope that readfile works
@readfile($filename);
$this->container->platform->closeApplication(0);
}
// If the filesize is reported, use 1M chunks for echoing the data to the browser
$blocksize = 1048576; //1M chunks
$handle = @fopen($filename, "r");
// Now we need to loop through the file and echo out chunks of file data
if ($handle !== false)
{
while (!@feof($handle))
{
echo @fread($handle, $blocksize);
@ob_flush();
if (!$this->noFlush)
{
flush();
}
}
}
if ($handle !== false)
{
@fclose($handle);
}
$this->container->platform->closeApplication(0);
}
/**
* Deletes one or more backup statistics records and their associated backup files
*/
public function remove()
{
// CSRF prevention
$this->csrfProtection();
$ids = $this->getIDsFromRequest();
if (empty($ids))
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');
return;
}
foreach ($ids as $id)
{
try
{
$msg = Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID');
$result = false;
if ($id > 0)
{
/** @var Statistics $model */
$model = $this->getModel();
$model->setState('id', $id);
$result = $model->delete();
}
}
catch (\RuntimeException $e)
{
$result = false;
$msg = $e->getMessage();
}
if (!$result)
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error');
return;
}
}
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_MSG_DELETED'));
}
/**
* Deletes backup files associated to one or several backup statistics records
*/
public function deletefiles()
{
// CSRF prevention
$this->csrfProtection();
$ids = $this->getIDsFromRequest();
if (empty($ids))
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');
return;
}
foreach ($ids as $id)
{
try
{
$msg = Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID');
$result = false;
if ($id > 0)
{
/** @var Statistics $model */
$model = $this->getModel();
$model->setState('id', $id);
$result = $model->deleteFile();
}
}
catch (\RuntimeException $e)
{
$result = false;
$msg = $e->getMessage();
}
if (!$result)
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error');
return;
}
}
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_MSG_DELETEDFILE'));
}
public function showcomment()
{
$ids = $this->getIDsFromRequest();
if (empty($ids))
{
$ids = [0];
}
$id = array_pop($ids);
if ($id <= 0)
{
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');
}
/** @var Statistics $model */
$model = $this->getModel();
$model->setState('id', $id);
$this->layout = 'comment';
$this->display(false);
}
/**
* Save the comments back to a backup record
*/
public function save()
{
// CSRF prevention
$this->csrfProtection();
$id = $this->input->get('id', 0, 'int');
$description = $this->input->get('description', '', 'string');
$comment = $this->input->get('comment', null, 'string', 4);
$statistic = Platform::getInstance()->get_statistics($id);
$statistic['description'] = $description;
$statistic['comment'] = $comment;
$result = Platform::getInstance()->set_or_update_statistics($id, $statistic);
$message = Text::_('COM_AKEEBA_BUADMIN_LOG_SAVEDOK');
$type = 'message';
if ($result === false)
{
$message = Text::_('COM_AKEEBA_BUADMIN_LOG_SAVEERROR');
$type = 'error';
}
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
}
public function restore()
{
// CSRF prevention
$this->csrfProtection();
$ids = $this->getIDsFromRequest();
if (empty($ids))
{
$ids = [0];
}
$id = array_pop($ids);
$url = Uri::base() . 'index.php?option=com_akeeba&view=Restore&id=' . $id;
$this->setRedirect($url);
}
public function cancel()
{
// CSRF prevention
$this->csrfProtection();
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage');
}
public function hidemodal()
{
/** @var Statistics $model */
$model = $this->getModel();
$model->hideRestorationInstructionsModal();
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage');
}
/**
* Freeze select records
*
* @throws Exception
*/
public function freeze()
{
$this->csrfProtection();
$ids = $this->getIDsFromRequest();
/** @var Statistics $model */
$model = $this->getModel();
$message = Text::_('COM_AKEEBA_BUADMIN_FREEZE_OK');
$type = 'message';
try
{
$model->freezeUnfreezeRecords($ids, 1);
}
catch (Exception $e)
{
$message = Text::sprintf('COM_AKEEBA_BUADMIN_FREEZE_ERROR', $e->getMessage());
$type = 'error';
}
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
}
/**
* Unfreeze select records
*
* @throws Exception
*/
public function unfreeze()
{
$this->csrfProtection();
$ids = $this->getIDsFromRequest();
/** @var Statistics $model */
$model = $this->getModel();
$message = Text::_('COM_AKEEBA_BUADMIN_UNFREEZE_OK');
$type = 'message';
try
{
$model->freezeUnfreezeRecords($ids, 0);
}
catch (Exception $e)
{
$message = Text::sprintf('COM_AKEEBA_BUADMIN_UNFREEZE_ERROR', $e->getMessage());
$type = 'error';
}
$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
}
/**
* Gets the list of IDs from the request data
*
* @return array
*/
protected function getIDsFromRequest()
{
// Get the ID or list of IDs from the request or the configuration
$cid = $this->input->get('cid', array(), 'array');
$id = $this->input->getInt('id', 0);
$ids = array();
if (is_array($cid) && !empty($cid))
{
$ids = $cid;
}
elseif (!empty($id))
{
$ids = array($id);
}
return $ids;
}
}
web.config 0000604 00000001025 15245617260 0006512 0 ustar 00 <?xml version="1.0"?>
<!--
This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
<system.webServer>
<security>
<requestFiltering>
<fileExtensions allowUnlisted="false" >
<clear />
<add fileExtension=".html" allowed="true"/>
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>