Current File : /home/pacjaorg/wpt.pacja.org/cop/libraries/fof40/Controller/DataController.php |
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Controller;
defined('_JEXEC') || die;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Exception\ItemNotFound;
use FOF40\Controller\Exception\LockedRecord;
use FOF40\Controller\Exception\NotADataModel;
use FOF40\Controller\Exception\NotADataView;
use FOF40\Controller\Exception\TaskNotFound;
use FOF40\Model\DataModel;
use FOF40\View\DataView\DataViewInterface;
use FOF40\View\View;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Response\JsonResponse;
/**
* Database-aware Controller
*
* @property-read \FOF40\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class DataController extends Controller
{
/**
* Variables that should be taken in account while working with the cache. You can set them in Controller
* constructor or inside onBefore* methods
*
* @var null|array
*/
protected $cacheParams = null;
/**
* An associative array for required ACL privileges per task. For example:
* array(
* 'edit' => 'core.edit',
* 'jump' => 'foobar.jump',
* 'alwaysallow' => 'true',
* 'neverallow' => 'false'
* );
*
* You can use the notation '@task' which means 'apply the same privileges as "task"'. If you create a reference
* back to yourself (e.g. 'mytask' => array('@mytask')) it will return TRUE.
*
* @var array
*/
protected $taskPrivileges = [
// Special privileges
'*editown' => 'core.edit.own', // Privilege required to edit own record
// Standard tasks
'add' => 'core.create',
'apply' => '&getACLForApplySave', // Apply task: call the getACLForApplySave method
'archive' => 'core.edit.state',
'cancel' => 'core.edit.state',
'copy' => '@add', // Maps copy ACLs to the add task
'edit' => 'core.edit',
'loadhistory' => '@edit', // Maps loadhistory ACLs to the edit task
'orderup' => 'core.edit.state',
'orderdown' => 'core.edit.state',
'publish' => 'core.edit.state',
'remove' => 'core.delete',
'forceRemove' => 'core.delete',
'save' => '&getACLForApplySave', // Save task: call the getACLForApplySave method
'savenew' => 'core.create',
'saveorder' => 'core.edit.state',
'trash' => 'core.edit.state',
'unpublish' => 'core.edit.state',
];
/**
* An indexed array of default values for the add task. Since the add task resets the model you can't set these
* values directly to the model. Instead, the defaultsForAdd values will be fed to model's bind() after it's reset
* and before the session-stored item data is bound to the model object.
*
* @var array
*/
protected $defaultsForAdd = [];
/**
* Public constructor of the Controller class. You can pass the following variables in the $config array,
* on top of what you already have in the base Controller class:
*
* taskPrivileges array ACL privileges for each task
* cacheableTasks array The cache-enabled tasks
*
* @param Container $container The application container
* @param array $config The configuration array
*/
public function __construct(Container $container, array $config = [])
{
parent::__construct($container, $config);
// Set up a default model name if none is provided
if (empty($this->modelName))
{
$this->modelName = $container->inflector->pluralize($this->view);
}
// Set up a default view name if none is provided
if (empty($this->viewName))
{
$this->viewName = $container->inflector->pluralize($this->view);
}
if (isset($config['cacheableTasks']))
{
if (!is_array($config['cacheableTasks']))
{
$config['cacheableTasks'] = explode(',', $config['cacheableTasks']);
$config['cacheableTasks'] = array_map('trim', $config['cacheableTasks']);
}
$this->cacheableTasks = $config['cacheableTasks'];
}
elseif ($this->container->platform->isBackend())
{
$this->cacheableTasks = [];
}
else
{
$this->cacheableTasks = ['browse', 'read'];
}
if (!isset($config['taskPrivileges']))
{
return;
}
if (!is_array($config['taskPrivileges']))
{
return;
}
$this->taskPrivileges = array_merge($this->taskPrivileges, $config['taskPrivileges']);
}
/**
* Executes a given controller task. The onBefore<task> and onAfter<task> methods are called automatically if they
* exist.
*
* If $task == 'default' we will determine the CRUD task to use based on the view name and HTTP verb in the request,
* overriding the routing.
*
* @param string $task The task to execute, e.g. "browse"
*
* @return null|bool False on execution failure
*
* @throws TaskNotFound When the task is not found
*/
public function execute(string $task)
{
if ($task == 'default')
{
$task = $this->getCrudTask();
}
return parent::execute($task);
}
/**
* Returns a named View object
*
* @param string $name The View name. If null we'll use the viewName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the View. If skipped
* we will use $this->config
*
* @return View|DataViewInterface The instance of the View known to this Controller
*
* @throws NotADataView If the View found does not implement the DataViewInterface
*/
public function getView(?string $name = null, array $config = [])
{
if (!empty($name))
{
$viewName = $name;
}
elseif (!empty($this->viewName))
{
$viewName = $this->viewName;
}
else
{
$viewName = $this->view;
}
if (!array_key_exists($viewName, $this->viewInstances))
{
if (empty($config) && isset($this->config['viewConfig']))
{
$config = $this->config['viewConfig'];
}
$viewType = $this->input->getCmd('format', 'html');
// Get the model's class name
$this->viewInstances[$viewName] = $this->container->factory->view($viewName, $viewType, $config);
}
if (($this->viewInstances[$viewName] instanceof View) && !($this->viewInstances[$viewName] instanceof DataViewInterface))
{
throw new NotADataView();
}
return $this->viewInstances[$viewName];
}
/**
* Implements a default browse task, i.e. read a bunch of records and send
* them to the browser.
*
* @return void
* @throws Exception
*/
public function browse(): void
{
// Initialise the savestate
$saveState = $this->input->get('savestate', -999, 'int');
if ($saveState == -999)
{
$saveState = true;
}
$this->getModel()->savestate($saveState);
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('browse', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record read. The id set in the request is passed to the model and
* then the item layout is used to render the result.
*
* @return void
*
* @throws ItemNotFound When the item is not found
* @throws Exception
*/
public function read(): void
{
// Load the model
/** @var DataModel $model */
$model = $this->getModel()->savestate(false);
// If there is no record loaded, try loading a record based on the id passed in the input object
if (!$model->getId())
{
$ids = $this->getIDsFromRequest($model);
if ($model->getId() != reset($ids))
{
$key = strtoupper($this->container->componentName . '_ERR_' . $model->getName() . '_NOTFOUND');
throw new ItemNotFound(Text::_($key), 404);
}
}
// Set the layout to item, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'item';
}
elseif ($this->layout == 'default')
{
$this->layout = 'item';
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('read', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record add. The form layout is used to present a blank page.
*
* @return void
* @throws Exception
*/
public function add(): void
{
// Load and reset the model
$model = $this->getModel()->savestate(false);
$model->reset();
// Set the layout to form, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'form';
}
elseif ($this->layout == 'default')
{
$this->layout = 'form';
}
if (!empty($this->defaultsForAdd))
{
$model->bind($this->defaultsForAdd);
}
// Get temporary data from the session, set if the save failed and we're redirected back here
$sessionKey = $this->viewName . '.savedata';
$itemData = $this->container->platform->getSessionVar($sessionKey, null, $this->container->componentName);
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
if (!empty($itemData))
{
$model->bind($itemData);
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('add', $this->cacheableTasks), $this->cacheParams);
}
/**
* Single record edit. The ID set in the request is passed to the model,
* then the form layout is used to edit the result.
*
* @return void
* @throws Exception
*/
public function edit(): void
{
// Load the model
/** @var DataModel $model */
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$userId = $this->container->platform->getUser()->id;
try
{
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->lock();
}
catch (Exception $e)
{
// Redirect on error
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
return;
}
// Set the layout to form, if it's not set in the URL
if (empty($this->layout))
{
$this->layout = 'form';
}
elseif ($this->layout == 'default')
{
$this->layout = 'form';
}
// Get temporary data from the session, set if the save failed and we're redirected back here
$sessionKey = $this->viewName . '.savedata';
$itemData = $this->container->platform->getSessionVar($sessionKey, null, $this->container->componentName);
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
if (!empty($itemData))
{
$model->bind($itemData);
}
// Display the view
$this->cacheParams = $this->cacheParams ?: null;
$this->display(in_array('edit', $this->cacheableTasks), $this->cacheParams);
}
/**
* Save the incoming data and then return to the Edit task
*
* @return void
* @throws Exception
*/
public function apply(): void
{
// CSRF prevention
$this->csrfProtection();
// Redirect to the edit task
if (!$this->applySave())
{
return;
}
$id = $this->input->get('id', 0, 'int');
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
}
/**
* Duplicates selected items
*
* @return void
* @throws Exception
*/
public function copy(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model);
$error = null;
$copiedCount = 0;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->copy();
$copiedCount++;
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
return;
}
if ($copiedCount > 0)
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_COPIED');
$this->setRedirect($url, Text::_($textKey));
return;
}
$this->setRedirect($url);
}
/**
* Save the incoming data and then return to the Browse task
*
* @return void
* @throws Exception
*/
public function save(): void
{
// CSRF prevention
$this->csrfProtection();
if (!$this->applySave())
{
return;
}
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
}
/**
* Save the incoming data and then return to the Add task
*
* @return bool
* @throws Exception
*/
public function savenew(): bool
{
// CSRF prevention
$this->csrfProtection();
if (!$this->applySave())
{
return false;
}
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_SAVED');
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->singularize($this->view) . '&task=add' . $this->getItemidURLSuffix();
$this->setRedirect($url, Text::_($textKey));
return true;
}
/**
* Save the incoming data as a copy of the given model and then redirect to the copied object edit view
*
* @return bool
* @throws Exception
*/
public function save2copy(): bool
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model);
$data = $this->input->getData();
unset($data[$model->getIdFieldName()]);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model = $model->copy($data);
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : $url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $model->getId() . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_COPIED');
$this->setRedirect($url, Text::_($textKey));
}
}
/**
* Cancel the edit, check in the record and return to the Browse task
*
* @return void
*/
public function cancel(): void
{
$model = $this->getModel()->tmpInstance()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
if ($model->getId())
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
try
{
$model->checkIn($userId);
}
catch (LockedRecord $e)
{
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
}
}
$model->unlock();
}
// Remove any saved data
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url);
}
/**
* Publish (set enabled = 1) an item.
*
* @return void
* @throws Exception
*/
public function publish(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = false;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->publish();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Unpublish (set enabled = 0) an item.
*
* @return void
* @throws Exception
*/
public function unpublish(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->unpublish();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Archive (set enabled = 2) an item.
*
* @return void
* @throws Exception
*/
public function archive(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->archive();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Trash (set enabled = -2) an item.
*
* @return void
* @throws Exception
*/
public function trash(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->trash();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Check in (unlock) items
*
* @return void
* @throws Exception
*/
public function checkin(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$model->checkIn();
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Saves the order of the items
*
* @return void
* @throws Exception
*/
public function saveorder(): void
{
// CSRF prevention
$this->csrfProtection();
$type = null;
$msg = null;
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$orders = $this->input->get('order', [], 'array');
// Before saving the order, I have to check I the table really supports the ordering feature
if (!$model->hasField('ordering'))
{
$msg = sprintf('%s does not support ordering.', $model->getTableName());
$type = 'error';
}
else
{
$ordering = $model->getFieldAlias('ordering');
// Several methods could throw exceptions, so let's wrap everything in a try-catch
try
{
if (($n = count($ids)) !== 0)
{
for ($i = 0; $i < $n; $i++)
{
$item = $model->find($ids[$i]);
$neworder = (int) $orders[$i];
if (!($item instanceof DataModel))
{
continue;
}
if ($item->getId() == $ids[$i])
{
$item->$ordering = $neworder;
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->save($item);
}
}
}
$model->reorder();
}
catch (Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $msg, $type);
}
/**
* Moves selected items one position down the ordering list
*
* @return void
* @throws Exception
*/
public function orderdown(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$error = null;
try
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->move(1);
$status = true;
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Moves selected items one position up the ordering list
*
* @return void
* @throws Exception
*/
public function orderup(): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$error = null;
try
{
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->move(-1);
$status = true;
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
}
/**
* Delete or trash selected item(s). The model's softDelete flag determines if the items should be trashed (enabled
* state changed to -2) or deleted (completely removed from database)
*
* @return void
*/
public function remove(): void
{
$this->deleteOrTrash();
}
/**
* Deletes the selected item(s). Unlike remove() this method will force delete the record (completely removed from
* database)
*
* @return void
*/
public function forceRemove(): void
{
$this->deleteOrTrash(true);
}
/**
* Returns a named Model object. Makes sure that the Model is a database-aware model, throwing an exception
* otherwise, when $name is null.
*
* @param string $name The Model name. If null we'll use the modelName
* variable or, if it's empty, the same name as
* the Controller
* @param array $config Configuration parameters to the Model. If skipped
* we will use $this->config
*
* @return DataModel The instance of the Model known to this Controller
*
* @throws NotADataModel When the model type doesn't match our expectations
*/
public function getModel(?string $name = null, array $config = [])
{
$model = parent::getModel($name, $config);
if (is_null($name) && !($model instanceof DataModel))
{
throw new NotADataModel('Model ' . get_class($model) . ' is not a database-aware Model');
}
return $model;
}
/**
* Gets the list of IDs from the request data
*
* @param DataModel $model The model where the record will be loaded
* @param bool $loadRecord When true, the record matching the *first* ID found will be loaded into $model
*
* @return int[]
*/
public function getIDsFromRequest(DataModel &$model, bool $loadRecord = true): array
{
// Get the ID or list of IDs from the request or the configuration
$cid = $this->input->get('cid', [], 'array');
$id = $this->input->getInt('id', 0);
$kid = $this->input->getInt($model->getIdFieldName(), 0);
$ids = [];
if (is_array($cid) && !empty($cid))
{
$ids = $cid;
}
elseif (empty($id))
{
if (!empty($kid))
{
$ids = [$kid];
}
}
else
{
$ids = [$id];
}
if ($loadRecord && !empty($ids))
{
$id = reset($ids);
$model->find(['id' => $id]);
}
return $ids;
}
/**
* Method to load a row from version history
*
* @return boolean True if the content history is reverted, false otherwise
*
* @since 2.2
*/
public function loadhistory(): bool
{
$model = $this->getModel();
$model->lock();
$historyId = $this->input->get('version_id', null, 'integer');
$alias = $this->container->componentName . '.' . $this->view;
$returnUrl = 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
if (!empty($customURL))
{
$returnUrl = $customURL;
}
try
{
$model->loadhistory($historyId, $alias);
}
catch (Exception $e)
{
$this->setRedirect($returnUrl, $e->getMessage(), 'error');
$model->unlock();
return false;
}
// Access check.
if (!$this->checkACL('@loadhistory'))
{
$this->setRedirect($returnUrl, Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
$model->unlock();
return false;
}
$model->store();
$this->setRedirect($returnUrl, Text::sprintf('JLIB_APPLICATION_SUCCESS_LOAD_HISTORY', $model->getState('save_date'), $model->getState('version_note')));
return true;
}
/**
* Gets a URL suffix with the Itemid parameter. If it's not the front-end of the site, or if
* there is no Itemid set it returns an empty string.
*
* @return string The &Itemid=123 URL suffix, or an empty string if Itemid is not applicable
*/
public function getItemidURLSuffix(): string
{
if ($this->container->platform->isFrontend() && ($this->input->getCmd('Itemid', 0) != 0))
{
return '&Itemid=' . $this->input->getInt('Itemid', 0);
}
else
{
return '';
}
}
/**
* Deal with JSON format: no redirects needed
*
* @param string $task The task being executed
*
* @return bool True if everything went well
*
* @throws Exception
*/
protected function onAfterExecute(string $task): bool
{
// JSON shouldn't have redirects
if ($this->hasRedirect() && $this->input->getCmd('format', 'html') == 'json')
{
// Error: deal with it in REST api way
if ($this->messageType == 'error')
{
$response = new JsonResponse($this->message, $this->message, true);
echo $response;
$this->redirect = false;
$this->container->platform->setHeader('Status', 500);
return true;
}
// Not an error, avoid redirect and display the record(s)
$this->redirect = false;
$this->display();
return true;
}
return true;
}
/**
* Determines the CRUD task to use based on the view name and HTTP verb used in the request.
*
* @return string The CRUD task (browse, read, edit, delete)
*/
protected function getCrudTask(): string
{
// By default, a plural view means 'browse' and a singular view means 'edit'
$view = $this->input->getCmd('view', null);
$task = $this->container->inflector->isPlural($view) ? 'browse' : 'edit';
// If the task is 'edit' but there's no logged in user switch to a 'read' task
if (($task == 'edit') && !$this->container->platform->getUser()->id)
{
$task = 'read';
}
// Check if there is an id passed in the request
$id = $this->input->get('id', null, 'int');
if ($id == 0)
{
$ids = $this->input->get('ids', [], 'array');
if (!empty($ids))
{
$id = array_shift($ids);
}
}
// Get the request HTTP verb
$requestMethod = 'GET';
if (isset($_SERVER['REQUEST_METHOD']))
{
$requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);
}
// Alter the task based on the verb
switch ($requestMethod)
{
// POST and PUT result in a record being saved; no ID means creating a new record
case 'POST':
case 'PUT':
$task = 'save';
break;
// DELETE results in a record being deleted, as long as there is an ID
case 'DELETE':
if ($id)
{
$task = 'remove';
}
break;
// GET results in browse, edit or add depending on the ID
case 'GET':
default:
// If it's an edit without an ID or ID=0, it's really an add
if (($task == 'edit') && ($id == 0))
{
$task = 'add';
}
break;
}
return $task;
}
/**
* Checks if the current user has enough privileges for the requested ACL area. This overridden method supports
* asset tracking as well.
*
* @param string $area The ACL area, e.g. core.manage
*
* @return boolean True if the user has the ACL privilege specified
*/
protected function checkACL(string $area): bool
{
$area = $this->getACLRuleFor($area);
$result = parent::checkACL($area);
// First, check if there is an asset for this record
/** @var DataModel $model */
$model = $this->getModel();
$ids = null;
if (is_object($model) && ($model instanceof DataModel) && $model->isAssetsTracked())
{
$ids = $this->getIDsFromRequest($model, false);
}
// No IDs tracked, return parent's result
if (empty($ids))
{
return $result;
}
// Asset tracking
if (!is_array($ids))
{
$ids = [$ids];
}
$resource = $this->container->inflector->singularize($this->view);
$isEditState = ($area == 'core.edit.state');
foreach ($ids as $id)
{
$asset = $this->container->componentName . '.' . $resource . '.' . $id;
// Dedicated permission found, check it!
$platform = $this->container->platform;
if ($platform->authorise($area, $asset))
{
return true;
}
// Fallback on edit.own, if not edit.state. First test if the permission is available.
$editOwn = $this->getACLRuleFor('@*editown');
if ((!$isEditState) && ($platform->authorise($editOwn, $asset)))
{
$model->load($id);
if (!$model->hasField('created_by'))
{
return false;
}
// Now test the owner is the user.
$owner_id = (int) $model->getFieldValue('created_by', null);
// If the owner matches 'me' then do the test.
return $owner_id === $platform->getUser()->id;
}
}
// No result found? Not authorised.
return false;
}
/**
* Automatically decides whether to trash or delete a record based in the $forceDelete parameter
*
* @param bool $forceDelete Should I delete the record? If false, the record will be trashed instead.
*
* @throws Exception
*/
protected function deleteOrTrash(bool $forceDelete = false): void
{
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
if ($forceDelete)
{
$model->forceDelete();
}
else
{
$model->delete();
}
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
// Redirect
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$textKey = strtoupper($this->container->componentName . '_LBL_' . $this->container->inflector->singularize($this->view) . '_DELETED');
$this->setRedirect($url, Text::_($textKey));
}
}
/**
* Common method to handle apply and save tasks
*
* @return bool True on success
*/
protected function applySave(): bool
{
// Load the model
$model = $this->getModel()->savestate(false);
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$userId = $this->container->platform->getUser()->id;
$id = $model->getId();
$data = $this->input->getData();
if ($model->isLocked($userId))
{
try
{
$model->checkIn($userId);
}
catch (LockedRecord $e)
{
// Redirect to the display task
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
$eventName = 'onAfterApplySaveError';
$result = $this->triggerEvent($eventName, [&$data, $id, $e]);
$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();
$this->setRedirect($url, $e->getMessage(), 'error');
return false;
}
}
// Set the layout to form, if it's not set in the URL
if (is_null($this->layout))
{
$this->layout = 'form';
}
// Save the data
$status = true;
$error = null;
try
{
$eventName = 'onBeforeApplySave';
$this->triggerEvent($eventName, [&$data]);
if ($id != 0)
{
// Try to check-in the record if it's not a new one
$model->unlock();
}
// Save the data
$model->save($data);
$eventName = 'onAfterApplySave';
$this->triggerEvent($eventName, [&$data, $model->getId()]);
$this->input->set('id', $model->getId());
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
$eventName = 'onAfterApplySaveError';
$result = $this->triggerEvent($eventName, [&$data, $model->getId(), $e]);
}
if (!$status)
{
// Cache the item data in the session. We may need to reuse them if the save fails.
$itemData = $model->getData();
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, $itemData, $this->container->componentName);
// Redirect on error
$id = $model->getId();
if (($customURL = $this->input->getBase64('returnurl', '') ?? '') !== '')
{
$customURL = base64_decode($customURL);
}
if (!empty($customURL))
{
$url = $customURL;
}
elseif ($id != 0)
{
$url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
}
else
{
$url = 'index.php?option=' . $this->container->componentName . '&view=' . $this->view . '&task=add' . $this->getItemidURLSuffix();
}
$this->setRedirect($url, $error, 'error');
}
else
{
$sessionKey = $this->viewName . '.savedata';
$this->container->platform->setSessionVar($sessionKey, null, $this->container->componentName);
}
return $status;
}
/**
* Gets the applicable ACL privilege for the apply and save tasks. The value returned is:
* - @add if the record's ID is empty / record doesn't exist
* - True if the ACL privilege of the edit task (@edit) is allowed
* - @editown if the owner of the record (field user_id, userid or user) is the same as the logged in user
* - False if the record is not owned by the logged in user and the user doesn't have the @edit privilege
*
* @return bool|string
*/
protected function getACLForApplySave()
{
$model = $this->getModel();
if (!$model->getId())
{
$this->getIDsFromRequest($model);
}
$id = $model->getId();
if (!$id)
{
return '@add';
}
if ($this->checkACL('@edit'))
{
return true;
}
$user = $this->container->platform->getUser();
$uid = 0;
if ($model->hasField('user_id'))
{
$uid = $model->getFieldValue('user_id');
}
elseif ($model->hasField('userid'))
{
$uid = $model->getFieldValue('userid');
}
elseif ($model->hasField('user'))
{
$uid = $model->getFieldValue('user');
}
if (!empty($uid) && !$user->guest && ($user->id == $uid))
{
return '@editown';
}
return false;
}
}