* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Privacy\Administrator\Controller; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\FormController; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Router\Route; use Joomla\CMS\Uri\Uri; use Joomla\Component\Privacy\Administrator\Model\ExportModel; use Joomla\Component\Privacy\Administrator\Model\RemoveModel; use Joomla\Component\Privacy\Administrator\Model\RequestModel; use Joomla\Component\Privacy\Administrator\Table\RequestTable; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Request management controller class. * * @since 3.9.0 */ class RequestController extends FormController { /** * Method to complete a request. * * @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 * * @since 3.9.0 */ public function complete($key = null, $urlVar = null) { // Check for request forgeries. $this->checkToken(); /** @var RequestModel $model */ $model = $this->getModel(); /** @var RequestTable $table */ $table = $model->getTable(); // 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); $item = $model->getItem($recordId); // Ensure this record can transition to the requested state if (!$this->canTransition($item, '2')) { $this->setMessage(Text::_('COM_PRIVACY_ERROR_COMPLETE_TRANSITION_NOT_PERMITTED'), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Build the data array for the update $data = [ $key => $recordId, 'status' => '2', ]; // Access check. if (!$this->allowSave($data, $key)) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Attempt to save the data. if (!$model->save($data)) { // Redirect back to the edit screen. $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Log the request completed $model->logRequestCompleted($recordId); $this->setMessage(Text::_('COM_PRIVACY_REQUEST_COMPLETED')); $url = 'index.php?option=com_privacy&view=requests'; // Check if there is a return value $return = $this->input->get('return', null, 'base64'); if (!is_null($return) && Uri::isInternal(base64_decode($return))) { $url = base64_decode($return); } // Redirect to the list screen. $this->setRedirect(Route::_($url, false)); return true; } /** * Method to email the data export for a request. * * @return boolean * * @since 3.9.0 */ public function emailexport() { // Check for request forgeries. $this->checkToken('get'); /** @var ExportModel $model */ $model = $this->getModel('Export'); $recordId = $this->input->getUint('id'); if (!$model->emailDataExport($recordId)) { // Redirect back to the edit screen. $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_EXPORT_EMAIL_FAILED', $model->getError()), 'error'); } else { $this->setMessage(Text::_('COM_PRIVACY_EXPORT_EMAILED')); } $url = 'index.php?option=com_privacy&view=requests'; // Check if there is a return value $return = $this->input->get('return', null, 'base64'); if (!is_null($return) && Uri::isInternal(base64_decode($return))) { $url = base64_decode($return); } // Redirect to the list screen. $this->setRedirect(Route::_($url, false)); return true; } /** * Method to export the data for a request. * * @return $this * * @since 3.9.0 */ public function export() { $this->input->set('view', 'export'); return $this->display(); } /** * Method to invalidate a request. * * @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 * * @since 3.9.0 */ public function invalidate($key = null, $urlVar = null) { // Check for request forgeries. $this->checkToken(); /** @var RequestModel $model */ $model = $this->getModel(); /** @var RequestTable $table */ $table = $model->getTable(); // 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); $item = $model->getItem($recordId); // Ensure this record can transition to the requested state if (!$this->canTransition($item, '-1')) { $this->setMessage(Text::_('COM_PRIVACY_ERROR_INVALID_TRANSITION_NOT_PERMITTED'), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Build the data array for the update $data = [ $key => $recordId, 'status' => '-1', ]; // Access check. if (!$this->allowSave($data, $key)) { $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Attempt to save the data. if (!$model->save($data)) { // Redirect back to the edit screen. $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } // Log the request invalidated $model->logRequestInvalidated($recordId); $this->setMessage(Text::_('COM_PRIVACY_REQUEST_INVALIDATED')); $url = 'index.php?option=com_privacy&view=requests'; // Check if there is a return value $return = $this->input->get('return', null, 'base64'); if (!is_null($return) && Uri::isInternal(base64_decode($return))) { $url = base64_decode($return); } // Redirect to the list screen. $this->setRedirect(Route::_($url, false)); return true; } /** * Method to remove the user data for a privacy remove request. * * @return boolean * * @since 3.9.0 */ public function remove() { // Check for request forgeries. $this->checkToken('request'); /** @var RemoveModel $model */ $model = $this->getModel('Remove'); $recordId = $this->input->getUint('id'); if (!$model->removeDataForRequest($recordId)) { // Redirect back to the edit screen. $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_REMOVE_DATA_FAILED', $model->getError()), 'error'); $this->setRedirect( Route::_( 'index.php?option=com_privacy&view=request&id=' . $recordId, false ) ); return false; } $this->setMessage(Text::_('COM_PRIVACY_DATA_REMOVED')); $url = 'index.php?option=com_privacy&view=requests'; // Check if there is a return value $return = $this->input->get('return', null, 'base64'); if (!is_null($return) && Uri::isInternal(base64_decode($return))) { $url = base64_decode($return); } // Redirect to the list screen. $this->setRedirect(Route::_($url, false)); return true; } /** * Function that allows child controller access to model data after the data has been saved. * * @param BaseDatabaseModel $model The data model object. * @param array $validData The validated data. * * @return void * * @since 3.9.0 */ protected function postSaveHook(BaseDatabaseModel $model, $validData = []) { // This hook only processes new items if (!$model->getState($model->getName() . '.new', false)) { return; } if (!$model->logRequestCreated($model->getState($model->getName() . '.id'))) { if ($error = $model->getError()) { $this->app->enqueueMessage($error, 'warning'); } } if (!$model->notifyUserAdminCreatedRequest($model->getState($model->getName() . '.id'))) { if ($error = $model->getError()) { $this->app->enqueueMessage($error, 'warning'); } } else { $this->app->enqueueMessage(Text::_('COM_PRIVACY_MSG_CONFIRM_EMAIL_SENT_TO_USER')); } } /** * Method to determine if an item can transition to the specified status. * * @param object $item The item being updated. * @param string $newStatus The new status of the item. * * @return boolean * * @since 3.9.0 */ private function canTransition($item, $newStatus) { switch ($item->status) { case '0': // A pending item can only move to invalid through this controller due to the requirement for a user to confirm the request return $newStatus === '-1'; case '1': // A confirmed item can be marked completed or invalid return in_array($newStatus, ['-1', '2'], true); // An item which is already in an invalid or complete state cannot transition, likewise if we don't know the state don't change anything case '-1': case '2': default: return false; } } }