| Current Path : /proc/1908984/root/proc/self/root/proc/self/root/proc/2411249/cwd/ |
| Current File : //proc/1908984/root/proc/self/root/proc/self/root/proc/2411249/cwd/Controller.tar |
Exception/CannotGetName.php 0000644 00000000521 15234466356 0011712 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Exception;
defined('_JEXEC') or die;
/**
* Exception thrown when we can't get a Controller's name
*/
class CannotGetName extends \RuntimeException {}
Exception/ItemNotFound.php 0000644 00000000537 15234466356 0011611 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Exception;
defined('_JEXEC') or die;
/**
* Exception thrown when we can't find the requested item in a read task
*/
class ItemNotFound extends \RuntimeException {}
Exception/LockedRecord.php 0000644 00000001141 15234466356 0011566 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Exception;
use Exception;
defined('_JEXEC') or die;
/**
* Exception thrown when the provided Model is locked for writing by another user
*/
class LockedRecord extends \RuntimeException
{
public function __construct($message = "", $code = 403, Exception $previous = null)
{
if (empty($message))
{
$message = \JText::_('LIB_FOF_CONTROLLER_ERR_LOCKED');
}
parent::__construct($message, $code, $previous);
}
}
Exception/NotADataModel.php 0000644 00000000536 15234466356 0011651 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Exception;
defined('_JEXEC') or die;
/**
* Exception thrown when the provided Model is not a DataModel
*/
class NotADataModel extends \InvalidArgumentException {}
Exception/NotADataView.php 0000644 00000000633 15234466356 0011521 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
{
}
Exception/TaskNotFound.php 0000644 00000000564 15234466356 0011615 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Exception;
defined('_JEXEC') or die;
/**
* Exception thrown when we can't find a suitable method to handle the requested task
*/
class TaskNotFound extends \InvalidArgumentException {}
Mixin/PredefinedTaskList.php 0000644 00000003620 15234466356 0012104 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller\Mixin;
use FOF30\Controller\Controller;
defined('_JEXEC') or die;
/**
* Force a Controller to allow access to specific tasks only, no matter which tasks are already defined in this
* Controller.
*
* Include this Trait and then in your constructor do this:
* $this->setPredefinedTaskList(['atask', 'anothertask', 'something']);
*
* WARNING: If you override execute() you will need to copy the logic from this trait's execute() method.
*/
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
*
* @return void
*/
public function setPredefinedTaskList(array $taskList)
{
/** @var Controller $this */
// 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));
}
}
Controller.php 0000644 00000071300 15234466356 0007417 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller;
use FOF30\Container\Container;
use FOF30\Controller\Exception\CannotGetName;
use FOF30\Controller\Exception\TaskNotFound;
use FOF30\Model\DataModel;
use FOF30\Model\Model;
use FOF30\View\View;
defined('_JEXEC') or die;
/**
* Class Controller
*
* A generic MVC controller implementation
*
* @property-read \FOF30\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class Controller
{
/**
* The name of the controller
*
* @var array
*/
protected $name = null;
/**
* The mapped task that was performed.
*
* @var string
*/
protected $doTask;
/**
* Bit mask to enable routing through JRoute 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
*/
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;
/**
* Instance container.
*
* @var Controller
*/
protected static $instance;
/**
* 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 = null;
/**
* A cached copy of the class configuration parameter passed during initialisation
*
* @var array
*/
protected $config = array();
/**
* Overrides the name of the view's default model
*
* @var string
*/
protected $modelName = null;
/**
* Overrides the name of the view's default view
*
* @var string
*/
protected $viewName = null;
/**
* An array of Model instances known to this Controller
*
* @var array[Model]
*/
protected $modelInstances = array();
/**
* An array of View instances known to this Controller
*
* @var array[View]
*/
protected $viewInstances = array();
/**
* The container attached to this Controller
*
* @var Container
*/
protected $container = null;
/**
* The tasks for which caching should be enabled by default
*
* @var array
*/
protected $cacheableTasks = array();
/**
* 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 = array();
/**
* 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 = array())
{
// Initialise
$this->methods = array();
$this->message = null;
$this->messageType = 'message';
$this->paths = array();
$this->redirect = null;
$this->taskMap = array();
// Get a local copy of the container
$this->container = $container;
// Determine the methods to exclude from the base class.
$xMethods = get_class_methods('\\FOF30\\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($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 null|bool False on execution failure
*
* @throws TaskNotFound When the task is not found
*/
public function execute($task)
{
$this->task = $task;
if (!isset($this->taskMap[$task]) && !isset($this->taskMap['__default']))
{
throw new TaskNotFound(\JText::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
}
$result = $this->triggerEvent('onBeforeExecute', array(&$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', array($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 bool $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
*/
public function display($cachable = false, $urlparams = false, $tpl = null)
{
$document = $this->container->platform->getDocument();
if ($document instanceof \JDocument)
{
$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 ($this->container->platform->isFrontend() && $cachable && ($viewType != 'feed') && ($conf->get('caching') >= 1))
{
// Get a JCache object
$option = $this->input->get('option', 'com_foobar', 'cmd');
/** @var \JCacheControllerView $cache */
$cache = \JFactory::getCache($option, 'view');
// Set up a cache ID based on component, view, task and user group assignment
$user = $this->container->platform->getUser();
if ($user->guest)
{
$groups = array();
}
else
{
$groups = $user->groups;
}
$importantParameters = array();
// Set up safe URL parameters
if (!is_array($urlparams))
{
$urlparams = array(
'option' => 'CMD',
'view' => 'CMD',
'task' => 'CMD',
'format' => 'CMD',
'layout' => 'CMD',
'id' => 'INT',
);
}
if (is_array($urlparams))
{
/** @var \JApplicationCms $app */
$app = \JFactory::getApplication();
$registeredurlparams = null;
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;
// 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(array(\JCache::makeId(), $view->getName(), $this->doTask, $groups, $importantParameters)));
// Get the cached view or cache the current view
$cache->get($view, 'display', $cacheId);
}
else
{
// Display without caching
$view->display($tpl);
}
}
/**
* Alias to the display() task
*
* @codeCoverageIgnore
*/
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($name = null, $config = array())
{
if (!empty($name))
{
$modelName = $name;
}
elseif (!empty($this->modelName))
{
$modelName = $this->modelName;
}
else
{
$modelName = $this->view;
}
if (!array_key_exists($modelName, $this->modelInstances))
{
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($name = null, $config = array())
{
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];
}
/**
* 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($viewName)
{
$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($modelName)
{
$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($modelName, Model &$model)
{
$this->modelInstances[$modelName] = $model;
}
/**
* 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($viewName, View &$view)
{
$this->viewInstances[$viewName] = $view;
}
/**
* 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()
{
if (empty($this->name))
{
$r = null;
if (!preg_match('/(.*)\\\\Controller\\\\(.*)/i', get_class($this), $r))
{
throw new CannotGetName(\JText::_('LIB_FOF_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()
{
return $this->task;
}
/**
* Gets the available tasks in the controller.
*
* @return array Array[i] of task names.
*/
public function getTasks()
{
return $this->methods;
}
/**
* Redirects the browser or returns false if no redirect is set.
*
* @return boolean False if no redirect exists.
*/
public function redirect()
{
if ($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($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 Controller This object to support chaining.
*/
public function registerTask($task, $method)
{
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($task)
{
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 Previous message
*/
public function setMessage($text, $type = 'message')
{
$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($url, $msg = null, $type = null)
{
// If we're parsing a non-SEF URL decide whether to use JRoute 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 = \JRoute::_($url, false);
}
}
// 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;
}
/**
* 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()
{
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;
break;
// 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');
\JLog::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.",
\JLog::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');
\JLog::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.",
\JLog::WARNING,
'deprecated'
);
$hasToken = $this->input->get('_token', null, 'none') == $token;
}
}
if (!$hasToken)
{
$platform->raiseError(403, \JText::_('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($event, array $arguments = array())
{
$result = true;
// If there is an object method for this event, call it
if (method_exists($this, $event))
{
switch (count($arguments))
{
case 0:
$result = $this->{$event}();
break;
case 1:
$result = $this->{$event}($arguments[0]);
break;
case 2:
$result = $this->{$event}($arguments[0], $arguments[1]);
break;
case 3:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2]);
break;
case 4:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
break;
case 5:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
break;
default:
$result = call_user_func_array(array($this, $event), $arguments);
break;
}
}
// If there is no handler method perform a simple ACL check
elseif (substr($event, 0, 8) == 'onBefore')
{
$task = substr($event, 8);
$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);
if (!empty($results))
{
foreach ($results as $result)
{
if ($result === false)
{
return false;
}
}
}
return 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($area)
{
$area = $this->getACLRuleFor($area);
if (is_bool($area))
{
return $area;
}
if (in_array(strtolower($area), array('false','0','no','403')))
{
return false;
}
if (in_array(strtolower($area), array('true','1','yes')))
{
return true;
}
if (in_array(strtolower($area), array('guest')))
{
return $this->container->platform->getUser()->guest;
}
if (in_array(strtolower($area), array('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 mixed The resolved ACL privilege
*/
protected function getACLRuleFor($area, $oldAreas = array())
{
// 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);
}
/**
* Returns true if there is a redirect set in the controller
*
* @return boolean
*/
public function hasRedirect()
{
return !empty($this->redirect);
}
}
DataController.php 0000644 00000117114 15234466356 0010215 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 2 or later
*/
namespace FOF30\Controller;
use FOF30\Container\Container;
use FOF30\Controller\Exception\ItemNotFound;
use FOF30\Controller\Exception\LockedRecord;
use FOF30\Controller\Exception\NotADataModel;
use FOF30\Controller\Exception\TaskNotFound;
use FOF30\Model\DataModel;
use FOF30\View\View;
defined('_JEXEC') or die;
/**
* Database-aware Controller
*
* @property-read \FOF30\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class DataController extends Controller
{
/**
* The tasks for which caching should be enabled by default
*
* @var array
*/
protected $cacheableTasks = array('browse', 'read');
/**
* Variables that should be taken in account while working with the cache. You can set them in Controller constructor
* or inside onBefore* methods
*
* @var false|array
*/
protected $cacheParams = false;
/**
* Do we have a valid XML form?
*
* @var bool
*/
protected $hasForm = false;
/**
* 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 = array(
// 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 = array();
/**
* 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 = array())
{
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'];
}
if (isset($config['taskPrivileges']) && is_array($config['taskPrivileges']))
{
$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($task)
{
if ($task == 'default')
{
$task = $this->getCrudTask();
}
return parent::execute($task);
}
/**
* Deal with JSON format: no redirects needed
* @param string $task The task being executed
* @return boolean True if everything went well
*/
protected function onAfterExecute($task)
{
// 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 \JResponseJson($this->message, $this->message, true);
echo $response;
$this->redirect = false;
$this->container->platform->setHeader('Status', 500);
return;
} else {
// Not an error, avoid redirect and display the record(s)
$this->redirect = false;
return $this->display();
}
}
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()
{
// 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(), '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($area)
{
$area = $this->getACLRuleFor($area);
$result = parent::checkACL($area);
// Check if we're dealing with ids
$ids = null;
// 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 = array($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.
if ($owner_id == $platform->getUser()->id)
{
return true;
}
return false;
}
}
// No result found? Not authorised.
return false;
}
/**
* 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($name = null, $config = array())
{
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');
if (($viewType == 'html') && $this->hasForm)
{
$viewType = 'form';
}
// Get the model's class name
$this->viewInstances[$viewName] = $this->container->factory->view($viewName, $viewType, $config);
}
return $this->viewInstances[$viewName];
}
/**
* Implements a default browse task, i.e. read a bunch of records and send
* them to the browser.
*
* @return void
*/
public function browse()
{
// Initialise the savestate
$saveState = $this->input->get('savestate', -999, 'int');
if ($saveState == -999)
{
$saveState = true;
}
$this->getModel()->savestate($saveState);
// Apply the Form name
$formName = 'form.default';
if (!empty($this->layout))
{
$formName = 'form.' . $this->layout;
}
$this->getModel()->setFormName($formName);
// Do we have a _valid_ form?
$form = $this->getModel()->getForm();
if ($form !== false)
{
$this->hasForm = true;
if (empty($this->layout))
{
$this->layout = 'default';
}
}
// Display the view
$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
*/
public function read()
{
// 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, true);
if ($model->getId() != reset($ids))
{
$key = strtoupper($this->container->componentName . '_ERR_' . $model->getName() . '_NOTFOUND');
throw new ItemNotFound(\JText::_($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';
}
// Apply the Form name
$formName = 'form.' . $this->layout;
$this->getModel()->setFormName($formName);
// Do we have a _valid_ form?
$form = $this->getModel()->getForm($model);
if ($form !== false)
{
$this->hasForm = true;
}
// Display the view
$this->display(in_array('read', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record add. The form layout is used to present a blank page.
*
* @return void
*/
public function add()
{
// 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);
}
// Apply the Form name
$formName = 'form.form';
if (!empty($this->layout))
{
$formName = 'form.' . $this->layout;
}
$this->getModel()->setFormName($formName);
// Do we have a _valid_ form?
$form = $this->getModel()->getForm($model);
if ($form !== false)
{
$this->hasForm = true;
}
// Display the view
$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
*/
public function edit()
{
// Load the model
/** @var DataModel $model */
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
$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);
}
// Apply the Form name
$formName = 'form.' . $this->layout;
$this->getModel()->setFormName($formName);
// Do we have a _valid_ form?
$form = $this->getModel()->getForm($model);
if ($form !== false)
{
$this->hasForm = true;
}
// Display the view
$this->display(in_array('edit', $this->cacheableTasks), $this->cacheParams);
}
/**
* Save the incoming data and then return to the Edit task
*
* @return void
*/
public function apply()
{
// 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, \JText::_($textKey));
}
/**
* Duplicates selected items
*
* @return void
*/
public function copy()
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, true);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->copy();
}
}
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) . '_COPIED');
$this->setRedirect($url, \JText::_($textKey));
}
}
/**
* Save the incoming data and then return to the Browse task
*
* @return void
*/
public function save()
{
// 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, \JText::_($textKey));
}
/**
* Save the incoming data and then return to the Add task
*
* @return bool
*/
public function savenew()
{
// 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->singularize($this->view) . '&task=add' . $this->getItemidURLSuffix();
$this->setRedirect($url, \JText::_($textKey));
}
/**
* Save the incoming data as a copy of the given model and then redirect to the copied object edit view
*
* @return bool
*/
public function save2copy()
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, true);
$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, \JText::_($textKey));
}
}
/**
* Cancel the edit, check in the record and return to the Browse task
*
* @return void
*/
public function cancel()
{
$model = $this->getModel()->tmpInstance()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
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
*/
public function publish()
{
// 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
*/
public function unpublish()
{
// 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
*/
public function archive()
{
// 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
*/
public function trash()
{
// 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
*/
public function checkin()
{
// 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
*/
public function saveorder()
{
// CSRF prevention
$this->csrfProtection();
$type = null;
$msg = null;
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$orders = $this->input->get('order', array(), '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))
{
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
*/
public function orderdown()
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
$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
*/
public function orderup()
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
$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()
{
$this->deleteOrTrash(false);
}
/**
* Deletes the selected item(s). Unlike remove() this method will force delete the record (completely removed from
* database)
*
* @return void
*/
public function forceRemove()
{
$this->deleteOrTrash(true);
}
protected function deleteOrTrash($forceDelete = false)
{
// 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, \JText::_($textKey));
}
}
/**
* Common method to handle apply and save tasks
*
* @return bool True on success
*/
protected function applySave()
{
// Load the model
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
$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, array(&$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';
}
// Apply the Form name
$formName = 'form.' . $this->layout;
$this->getModel()->setFormName($formName);
// Save the data
$status = true;
$error = null;
try
{
$eventName = 'onBeforeApplySave';
$result = $this->triggerEvent($eventName, array(&$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';
$result = $this->triggerEvent($eventName, array(&$data, $model->getId()));
$this->input->set('id', $model->getId());
}
catch (\Exception $e)
{
$status = false;
$error = $e->getMessage();
$eventName = 'onAfterApplySaveError';
$result = $this->triggerEvent($eventName, array(&$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;
}
/**
* 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($name = null, $config = array())
{
$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 array
*/
public function getIDsFromRequest(DataModel &$model, $loadRecord = true)
{
// 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);
$kid = $this->input->getInt($model->getIdFieldName(), 0);
$ids = array();
if (is_array($cid) && !empty($cid))
{
$ids = $cid;
}
else
{
if (empty($id))
{
if(!empty($kid))
{
$ids = array($kid);
}
}
else
{
$ids = array($id);
}
}
if ($loadRecord && !empty($ids))
{
$id = reset($ids);
$model->find(array('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()
{
$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, \JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
$model->unlock();
return false;
}
$model->store();
$this->setRedirect($returnUrl, \JText::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()
{
if ($this->container->platform->isFrontend() && ($this->input->getCmd('Itemid', 0) != 0))
{
return '&Itemid=' . $this->input->getInt('Itemid', 0);
}
else
{
return '';
}
}
/**
* 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, true);
}
$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;
}
}
Mixin/ActivateProfile.php 0000644 00000002316 15234533440 0011427 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\Site\Controller\Mixin;
// Protect from unauthorized access
use Akeeba\Engine\Platform;
defined('_JEXEC') || die();
/**
* Provides the method to set the current backup profile from the request variables
*/
trait ActivateProfile
{
/**
* Set the active profile from the input parameters
*/
protected function setProfile()
{
$profile = $this->input->get('profile', 1, 'int');
$profile = max(1, $profile);
$this->container->platform->setSessionVar('profile', $profile, '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($profile);
}
}
Mixin/CustomRedirection.php 0000644 00000001531 15234533440 0012006 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\Site\Controller\Mixin;
// Protect from unauthorized access
use Akeeba\Engine\Platform;
defined('_JEXEC') || die();
/**
* Provides the method to send custom HTTP redirection headers
*/
trait CustomRedirection
{
/**
* Sends custom HTTP redirection headers
*
* @param string $url The URL to redirect to
* @param string $header The HTTP header to send, default 302 Found
*/
protected function customRedirect($url, $header = '302 Found')
{
header('HTTP/1.1 ' . $header);
header('Location: ' . $url);
header('Content-Type: text/plain');
header('Connection: close');
$this->container->platform->closeApplication();
}
}
Mixin/FrontEndPermissions.php 0000644 00000004045 15234533440 0012322 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\Site\Controller\Mixin;
// Protect from unauthorized access
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\Complexify;
use DateInterval;
use Exception;
use FOF40\Date\Date;
use Joomla\CMS\Language\Text;
defined('_JEXEC') || die();
/**
* Provides the method to check whether front-end backup is enabled and weather the key is correct
*/
trait FrontEndPermissions
{
private static $ENABLE_DATE_CHECKS = false;
/**
* Check that the user has sufficient permissions to access the front-end backup feature.
*
* @return void
*/
protected function checkPermissions()
{
// Is frontend backup enabled?
$febEnabled = $this->container->params->get('legacyapi_enabled', 0) == 1;
// Is the Secret Key strong enough?
$validKey = Platform::getInstance()->get_platform_configuration_option('frontend_secret_word', '');
$validKeyTrim = trim($validKey);
if (!Complexify::isStrongEnough($validKey, false))
{
$febEnabled = false;
}
if (static::$ENABLE_DATE_CHECKS && !$this->confirmDates())
{
@ob_end_clean();
echo '402 Your version of Akeeba Backup is too old. Please update it to re-enable the remote backup features';
flush();
$this->container->platform->closeApplication();
}
// Is the key good?
$key = $this->input->get('key', '', 'none', 2);
if (!$febEnabled || ($key != $validKey) || (empty($validKeyTrim)))
{
@ob_end_clean();
echo sprintf("403 %s", Text::_('COM_AKEEBA_COMMON_ERR_NOT_ENABLED'));
flush();
$this->container->platform->closeApplication();
}
}
private function confirmDates()
{
if (!defined('AKEEBA_DATE'))
{
return false;
}
try
{
$jDate = new Date(AKEEBA_DATE);
$interval = new DateInterval('P4M');
$jFuture = $jDate->add($interval);
$futureTS = $jFuture->toUnix();
}
catch (Exception $e)
{
return false;
}
return time() <= $futureTS;
}
}
Api.php 0000644 00000012727 15234533440 0006002 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\Site\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Site\Model\Json\Task;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\Complexify;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Controller\Mixin\PredefinedTaskList;
use FOF40\Input\Input;
use Joomla\CMS\Document\Document;
use Joomla\CMS\Document\JsonDocument;
use Joomla\CMS\Factory;
use JsonSerializable;
/**
* API version
*
* 400: First JSON API v2 implementation
*/
if (!defined('AKEEBA_JSON_API_VERSION'))
{
define('AKEEBA_JSON_API_VERSION', 400);
}
/**
* Akeeba Backup JSON API v2
*
* @since 7.4.0
*/
class Api extends Controller
{
use PredefinedTaskList;
/**
* Secret Key (cached for quicker retrieval)
*
* @var null|string
* @since 7.4.0
*/
private $key = null;
/**
* Overridden constructor
*
* @param Container $container The application container
* @param array $config The configuration array
*
* @since 7.4.0
*/
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['main']);
}
public function main()
{
if (!defined('AKEEBA_BACKUP_ORIGIN'))
{
define('AKEEBA_BACKUP_ORIGIN', 'json');
}
$outputBuffering = function_exists('ob_start') && function_exists('ob_end_clean');
// Use the model to parse the JSON message
if ($outputBuffering)
{
@ob_start();
}
try
{
if (!$this->verifyKey())
{
throw new \RuntimeException("Access denied", 503);
}
$httpVerb = $this->input->getMethod() ?? 'GET';
switch ($httpVerb)
{
case 'GET':
$method = $this->input->get->getCmd('method', '');
$input = new Input('GET');
break;
case 'POST':
$method = $this->input->post->getCmd('method', '');
$input = new Input('POST');
break;
default:
throw new \RuntimeException("Invalid HTTP method {$httpVerb}", 405);
break;
}
if (class_exists('Joomla\CMS\Component\ComponentHelper') && \Joomla\CMS\Component\ComponentHelper::isEnabled('com_akeebabackup'))
{
throw new \RuntimeException(sprintf('Please finish upgrading to Akeeba Backup 9 and uninstall Akeeba Backup 8 per the instructions shown on your site\'s backend, Components, Akeeba Backup'), 400);
}
$taskHandler = new Task($this->container);
$result = [
'status' => 200,
'data' => $taskHandler->execute($method, $input->getData())
];
}
catch (Exception $e)
{
$result = [
'status' => $e->getCode(),
'data' => $e->getMessage(),
];
}
if ($outputBuffering)
{
@ob_end_clean();
}
/** @var JsonDocument $doc */
$doc = Document::getInstance('json');
if (!($doc instanceof JsonDocument))
{
$this->workaroundResponse($result);
}
// Force cache busting
$app = $this->container->platform;
$app->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);
$app->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', true);
$app->setHeader('Pragma', 'no-cache', true);
$doc->setName('akeeba');
$jsonOptions = (defined('JDEBUG') && JDEBUG) ? JSON_PRETTY_PRINT : 0;
echo json_encode($result, $jsonOptions);
}
/**
* Send a JSON response when format=html or anything other than json
*
* @param JsonSerializable|array $result
*
* @throws Exception
*
* @since 7.4.0
*/
private function workaroundResponse($result): void
{
// Disable caching
@header('Expires: Wed, 17 Aug 2005 00:00:00 GMT', true);
@header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0', true);
@header('Pragma: no-cache', true);
// JSON content
@header('Content-Type: application/json; charset=utf-8', true);
@header('Content-Disposition: attachment; filename="joomla.json"', true);
$jsonOptions = (defined('JDEBUG') && JDEBUG) ? JSON_PRETTY_PRINT : 0;
echo json_encode($result, $jsonOptions);
Factory::getApplication()->close();
}
/**
* Verifies the Secret Key (API token)
*
* @return bool
* @since 7.4.0
*/
private function verifyKey(): bool
{
// Is the JSON API enabled?
if ($this->container->params->get('jsonapi_enabled', 0) != 1)
{
return false;
}
// Is the key secure enough?
$validKey = $this->serverKey();
if (empty($validKey) || empty(trim($validKey)) || !Complexify::isStrongEnough($validKey, false))
{
return false;
}
/**
* Get the API authentication token. There are two sources
* 1. X-Akeeba-Auth header (preferred, overrides all others)
* 2. the _akeebaAuth GET parameter
*/
$authSource = $this->input->server->getString('HTTP_X_AKEEBA_AUTH', null);
if (is_null($authSource))
{
$authSource = $this->input->get->getString('_akeebaAuth', null);
}
// No authentication token? No joy.
if (empty($authSource) || !is_string($authSource) || empty(trim($authSource)))
{
return false;
}
return hash_equals($validKey, $authSource);
}
/**
* Get the server key, i.e. the Secret Word for the front-end backups and JSON API
*
* @return mixed
*
* @since 7.4.0
*/
private function serverKey()
{
if (is_null($this->key))
{
$this->key = Platform::getInstance()->get_platform_configuration_option('frontend_secret_word', '');
}
return $this->key;
}
}
Backup.php 0000644 00000012701 15234533440 0006466 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\Site\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Site\Controller\Mixin\ActivateProfile;
use Akeeba\Backup\Site\Controller\Mixin\CustomRedirection;
use Akeeba\Backup\Site\Controller\Mixin\FrontEndPermissions;
use Akeeba\Engine\Factory;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Controller\Mixin\PredefinedTaskList;
use FOF40\Date\Date;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
if (!defined('AKEEBA_BACKUP_ORIGIN'))
{
define('AKEEBA_BACKUP_ORIGIN', 'frontend');
}
/**
* Controller for the front-end backup feature.
*
* The Traits used by this class offer most of the features you don't see, especially those pertaining to security:
* PredefinedTaskList Only allows certain tasks to be called.
* FrontEndPermissions Validates the secret word before running a task through checkPermissions.
* ActivateProfile Finds the profile specified in the URL and loads it through setProfile.
* CustomRedirection Provides customRedirect for HTTP redirects without dealing with CMS inconsistencies.
*/
class Backup extends Controller
{
use PredefinedTaskList, FrontEndPermissions, ActivateProfile, CustomRedirection;
/**
* Overridden constructor
*
* @param Container $container The application container
* @param array $config The configuration array
*/
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['main', 'step']);
}
/**
* Start a front-end legacy backup
*
* @return void
*/
public function main()
{
$this->checkPermissions();
$this->setProfile();
if (class_exists('Joomla\CMS\Component\ComponentHelper') && \Joomla\CMS\Component\ComponentHelper::isEnabled('com_akeebabackup'))
{
@ob_end_clean();
echo '500 ERROR -- Please finish upgrading to Akeeba Backup 9 and uninstall Akeeba Backup 8 per the instructions shown on your site\'s backend, Components, Akeeba Backup';
flush();
$this->container->platform->closeApplication();
}
// Get the backup ID
$backupId = $this->input->get('backupid', null, 'cmd');
if (empty($backupId))
{
$backupId = null;
}
/** @var \Akeeba\Backup\Site\Model\Backup $model */
$model = $this->container->factory->model('Backup')->tmpInstance();
$dateNow = new Date();
$model->setState('tag', AKEEBA_BACKUP_ORIGIN);
$model->setState('backupid', $backupId);
$model->setState('description', $model->getDefaultDescription() . ' (Frontend)');
$model->setState('comment', '');
$array = $model->startBackup();
$backupId = $model->getState('backupid', null, 'cmd');
$this->processEngineReturnArray($array, $backupId);
}
/**
* Step through a front-end legacy backup
*
* @return void
*/
public function step()
{
// Setup
$this->checkPermissions();
$this->setProfile();
// Get the backup ID
$backupId = $this->input->get('backupid', null, 'cmd');
if (empty($backupId))
{
$backupId = null;
}
/** @var \Akeeba\Backup\Site\Model\Backup $model */
$model = $this->container->factory->model('Backup')->tmpInstance();
$model->setState('tag', AKEEBA_BACKUP_ORIGIN);
$model->setState('backupid', $backupId);
$array = $model->stepBackup();
$backupId = $model->getState('backupid', null, 'cmd');
$this->processEngineReturnArray($array, $backupId);
}
/**
* Used by the tasks to process Akeeba Engine's return array. Depending on the result and the component options we
* may throw text output or send an HTTP redirection header.
*
* @param array $array The return array to process
* @param string $backupId The backup ID (used to step the backup process)
*/
private function processEngineReturnArray($array, $backupId)
{
if ($array['Error'] != '')
{
@ob_end_clean();
echo '500 ERROR -- ' . $array['Error'];
flush();
$this->container->platform->closeApplication();
}
if ($array['HasRun'] == 1)
{
// All done
Factory::nuke();
Factory::getFactoryStorage()->reset();
@ob_end_clean();
header('Content-type: text/plain');
header('Connection: close');
echo '200 OK';
flush();
$this->container->platform->closeApplication();
}
$noredirect = $this->input->get('noredirect', 0, 'int');
if ($noredirect != 0)
{
@ob_end_clean();
header('Content-type: text/plain');
header('Connection: close');
echo "301 More work required -- BACKUPID ###$backupId###";
flush();
$this->container->platform->closeApplication();
}
$curUri = Uri::getInstance();
$ssl = $curUri->isSSL() ? 1 : 0;
$tempURL = Route::_('index.php?option=com_akeeba', false, $ssl);
$uri = new Uri($tempURL);
$uri->delVar('key');
$uri->setVar('view', 'Backup');
$uri->setVar('task', 'step');
$uri->setVar('profile', $this->input->get('profile', 1, 'int'));
if (!empty($backupId))
{
$uri->setVar('backupid', $backupId);
}
// Maybe we have a multilingual site?
$language = $this->container->platform->getLanguage();
$languageTag = $language->getTag();
$uri->setVar('lang', $languageTag);
$key = $this->input->get('key', '', 'none', 2);
$redirectionUrl = $uri->toString() . '&key=' . urlencode($key);
$this->customRedirect($redirectionUrl);
}
}
Check.php 0000644 00000003004 15234533440 0006272 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\Site\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use Akeeba\Backup\Site\Controller\Mixin\FrontEndPermissions;
use Akeeba\Backup\Site\Model\Statistics;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Controller\Mixin\PredefinedTaskList;
/**
* Controller for the front-end Check Backups features
*/
class Check extends Controller
{
use PredefinedTaskList, FrontEndPermissions;
/**
* Overridden constructor
*
* @param Container $container The application container
* @param array $config The configuration array
*/
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['main']);
}
/**
* Checks for failed backups and sends out any notification emails
*/
public function main()
{
// Check permissions
$this->checkPermissions();
/** @var Statistics $model */
$model = $this->container->factory->model('Statistics')->tmpInstance();
$result = $model->notifyFailed();
$message = $result['result'] ? '200 ' : '500 ';
$message .= implode(', ', $result['message']);
@ob_end_clean();
header('Content-type: text/plain');
header('Connection: close');
echo $message;
flush();
$this->container->platform->closeApplication();
}
}
Json.php 0000644 00000002657 15234533440 0006203 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\Site\Controller;
// Protect from unauthorized access
defined('_JEXEC') || die();
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Controller\Mixin\PredefinedTaskList;
/**
* Controller for the JSON API
*/
class Json extends Controller
{
use PredefinedTaskList;
/**
* Overridden constructor
*
* @param Container $container The application container
* @param array $config The configuration array
*/
public function __construct(Container $container, array $config)
{
parent::__construct($container, $config);
$this->setPredefinedTaskList(['json']);
}
/**
* Handles API calls
*/
public function json()
{
// Use the model to parse the JSON message
if (function_exists('ob_start'))
{
@ob_start();
}
$sourceJSON = $this->input->get('json', null, 'raw', 2);
/** @var \Akeeba\Backup\Site\Model\Json $model */
$model = $this->getModel();
$json = $model->execute($sourceJSON);
if (function_exists('ob_end_clean'))
{
@ob_end_clean();
}
// Just dump the JSON and tear down the application, without plugins executing
header('Content-type: text/plain');
header('Connection: close');
echo $json;
$this->container->platform->closeApplication();
}
}
index.html 0000644 00000000352 15234533440 0006544 0 ustar 00 <!--~
~ @package akeebabackup
~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
~ @license GNU General Public License version 3, or later
-->
<html><head><title></title></head><body></body></html>