Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/Controller.tar
Назад
Controller.php 0000604 00000075466 15245531134 0007421 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller; defined('_JEXEC') || die; use FOF30\Container\Container; use FOF30\Controller\Exception\CannotGetName; use FOF30\Controller\Exception\TaskNotFound; use FOF30\Model\Model; use FOF30\View\View; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Cache\Cache; use Joomla\CMS\Cache\Controller\ViewController; use Joomla\CMS\Cache\Exception\CacheExceptionInterface; use Joomla\CMS\Document\Document; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Router\Route; use Joomla\CMS\Uri\Uri; /** * 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 { /** * Instance container. * * @var Controller */ protected static $instance; /** * 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; /** * 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 = []; /** * 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 = []; /** * An array of View instances known to this Controller * * @var array[View] */ protected $viewInstances = []; /** * 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 = []; /** * 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('\\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(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 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 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 JCache 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(); if ($user->guest) { $groups = []; } else { $groups = $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 = Factory::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([ Cache::makeId(), $view->getName(), $this->doTask, $groups, $userId, $importantParameters, ])); // Get the cached view or cache the current view try { /** @var ViewController $cache */ $cache = Factory::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 */ 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 = []) { 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 = []) { 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($viewName, View &$view) { $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($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; } /** * 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(Text::_('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 = 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() { 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() { 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'); 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->raiseError(403, 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($event, array $arguments = []) { $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([$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), ['false', '0', 'no', '403'])) { return false; } if (in_array(strtolower($area), ['true', '1', 'yes'])) { return true; } if (in_array(strtolower($area), ['guest'])) { return $this->container->platform->getUser()->guest; } if (in_array(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 mixed The resolved ACL privilege */ protected function getACLRuleFor($area, $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 00000003631 15245531134 0012067 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Mixin; defined('_JEXEC') || die; use FOF30\Controller\Controller; /** * 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 = []; /** * 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)); } } DataController.php 0000604 00000114007 15245531134 0010174 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller; defined('_JEXEC') || die; 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; use Joomla\CMS\Language\Text; use Joomla\CMS\Response\JsonResponse; /** * Database-aware Controller * * @property-read \FOF30\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 false|array */ protected $cacheParams = 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 = [ // 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']) && 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); } /** * 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 = []) { 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]; } /** * 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); // 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(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->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); } // 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); } // 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, Text::_($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; $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 */ 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, Text::_($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, Text::_($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, Text::_($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'); // 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); } /** * 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 = []) { $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'); $id = $this->input->getInt('id', 0); $kid = $this->input->getInt($model->getIdFieldName(), 0); $ids = []; if (is_array($cid) && !empty($cid)) { $ids = $cid; } else { if (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() { $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() { 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 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 JsonResponse($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'); 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 = [$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; } 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, Text::_($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, [&$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'; $result = $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'; $result = $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, 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; } } Exception/ItemNotFound.php 0000604 00000000556 15245531134 0011573 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Exception; defined('_JEXEC') || die; /** * Exception thrown when we can't find the requested item in a read task */ class ItemNotFound extends \RuntimeException { } Exception/CannotGetName.php 0000604 00000000540 15245531134 0011674 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Exception; defined('_JEXEC') || die; /** * Exception thrown when we can't get a Controller's name */ class CannotGetName extends \RuntimeException { } Exception/LockedRecord.php 0000604 00000001213 15245531134 0011547 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Exception; defined('_JEXEC') || die; use Exception; use Joomla\CMS\Language\Text; /** * 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 = Text::_('LIB_FOF_CONTROLLER_ERR_LOCKED'); } parent::__construct($message, $code, $previous); } } Exception/NotADataModel.php 0000604 00000000555 15245531134 0011633 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Exception; defined('_JEXEC') || die; /** * Exception thrown when the provided Model is not a DataModel */ class NotADataModel extends \InvalidArgumentException { } Exception/TaskNotFound.php 0000604 00000000603 15245531134 0011570 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Controller\Exception; defined('_JEXEC') || die; /** * 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>